即使进程正在运行,Process.HasExited 也会返回 true?

Posted

技术标签:

【中文标题】即使进程正在运行,Process.HasExited 也会返回 true?【英文标题】:Process.HasExited returns true even though process is running? 【发布时间】:2011-01-31 22:59:00 【问题描述】:

我一直观察到Process.HasExited 有时会返回true,即使进程仍在运行。

我下面的代码启动了一个名为“testprogram.exe”的进程,然后等待它退出。问题是有时我会抛出异常;似乎即使HasExited 返回true 进程本身在系统中仍然存在 - 这怎么可能??

我的程序在它终止之前写入一个日志文件,因此我需要在读取它之前绝对确定这个日志文件存在(也就是进程已经终止/完成)。不断检查它的存在不是一种选择。

// Create new process object
process = new Process();

// Setup event handlers
process.EnableRaisingEvents = true;
process.OutputDataReceived += OutputDataReceivedEvent;
process.ErrorDataReceived += ErrorDataReceivedEvent;
process.Exited += ProgramExitedEvent;

// Setup start info
ProcessStartInfo psi = new ProcessStartInfo
                           
                               FileName = ExePath,
                               // Must be false to redirect IO
                               UseShellExecute = false,
                               RedirectStandardOutput = true,
                               RedirectStandardError = true,
                               Arguments = arguments
                           ;

process.StartInfo = psi;

// Start the program
process.Start();

while (!process.HasExited)
    Thread.Sleep( 500 );

Process[] p = Process.GetProcessesByName( "testprogram" );

if ( p.Length != 0 )
    throw new Exception("Oh oh");

更新:我只是尝试使用 process.WaitForExit() 而不是轮询循环等待,结果完全相同。

补充:上面的代码只是为了演示一个“更清晰”的问题。说清楚;我的问题不在于在将HasExited 设置为true 之后,我仍然可以通过Process.GetProcessesByName( "testprogram" ); 来控制该过程。

真正的问题是我在外部运行的程序在它终止之前写入了一个文件(优雅地)。我使用HasExited 检查进程何时完成,因此我知道我可以读取文件(因为进程已退出!),但似乎HasExited 返回true,即使有时程序没有写入文件到磁盘呢。这是说明确切问题的示例代码:

// Start the program
process.Start();

while (!process.HasExited)
    Thread.Sleep( 500 );
// Could also be process.WaitForExit(), makes no difference to the result

// Now the process has quit, I can read the file it has exported
if ( !File.Exists( xmlFile ) )

    // But this exception is thrown occasionally, why?
    throw new Exception("xml file not found");

【问题讨论】:

testprogram.exe 是如何退出的?您是在上面调用 Kill(),还是过早地结束它?完成所有写入后是否正常退出? 我不知道。这是我正在运行的外部程序,我没有它的源代码。我所知道的是它在关闭之前正在拧文件(导出其结果),因为这就是我从中观察到的行为。返回值为 0 所以我猜它正常退出。没有其他任何显示。 @johnrl 你有没有解决过这个问题,你能分享你的代码吗 如果您最终想要的是输出的 xml 文件,您是否考虑过 FileSystemWatcher? msdn.microsoft.com/en-us/library/… @johnrl 我想知道是否一切都如您所愿......我从来没有遇到过管理这样的流程的问题。也许它启动了一个子进程并退出了主进程,或者由于某种原因 pid 发生了变化,或者在 Windows 错误报告中工作时在另一个进程中完成了转储,或者......变量太多了。您可以使用 procmon (sysinternals) 来获取有关什么进程做什么的更多信息,并检查 pid 是否在进程资源管理器中(来自 sysinternals 的 procexp)吗? 【参考方案1】:

我知道,这是一篇旧帖子,但也许我可以帮助某人。 Process 类可能会出现意外行为。如果进程已退出,HasExited 将返回 true如果进程以管理员权限运行并且您的程序只有 用户权限

不久前我在here 上发布了一个关于此的问题,但没有得到令人满意的答案。

【讨论】:

【参考方案2】:

我意识到这是一篇旧帖子,但在我试图找出为什么我的应用在应用打开之前运行 Exited 事件的过程中,我发现了一些我认为可能对将来遇到此问题的人有用的东西。

当一个进程启动时,它被分配一个 PID。 如果用户随后收到“用户帐户控制”对话框的提示并选择“是”,则该进程将重新启动并分配一个新的 PID。

我坐了几个小时,希望这可以节省一些时间。

【讨论】:

【参考方案3】:

在检查进程是否退出之前使用process_name.Refresh()Refresh() 将清除所有与进程相关的缓存信息。

【讨论】:

我看到这被否决了 - 关于原因的任何见解?我遇到了与 OP 类似的问题,我只是想对所有这些答案进行排序......【参考方案4】:

如果您有 Web 应用程序,并且您的外部程序/进程正在生成文件(写入磁盘),请检查您的 IIS 是否有权写入该文件夹(如果不在属性安全性上为您的 IIS 用户添加权限),这就是原因在我的情况下,我正在接收 process.HasExited =true,但是从该进程生成的文件没有完成,经过一段时间的努力,我将完全权限添加到进程正在扭动的文件夹和 process.Refresh(),正如上面描述的 Zarathos一切都按预期进行。

【讨论】:

【参考方案5】:

首先,您确定 testprogram 不会产生自己的进程并在不等待该进程完成的情况下退出?我们在这里处理某种竞争条件,而 testprogram 可能很重要。

我想说的第二点是关于这个 - “我需要绝对确定这个日志文件存在”。好吧,没有这样的事情。您可以进行检查,然后文件就消失了。解决此问题的常用方法不是检查,而是对文件执行您想要执行的操作。继续,阅读它,捕获异常,如果事情看起来不稳定并且您不想更改任何内容,请重试。如果系统中有多个参与者(线程或其他),则功能检查和执行将无法正常工作。

随之而来的是一堆随机的想法。

您是否尝试过使用 FileSystemWatcher 而不是依赖于进程完成?

如果您尝试在 process.Exited 事件中读取文件(不检查它是否存在,而是 正在执行),它会变得更好吗? [不应该]

系统是否健康?事件日志中有什么可疑之处吗?

是否可以涉及一些非常激进的防病毒策略?

(如果不查看所有代码并查看测试程序,则无法说明太多。)

【讨论】:

【参考方案6】:

因此,为了进一步调查问题的根本原因,您或许应该使用Process Monitor 来检查实际发生的情况。只需启动它并包含外部程序和您自己的工具并让它记录发生的情况。

在日志中,您应该看到外部工具如何写入输出文件以及您如何打开该文件。但在此日志中,您应该看到所有这些访问发生的顺序。

我想到的第一件事是Process 类不会说谎,当它告诉我时,这个过程真的消失了。所以问题是,在这个时间点,文件似乎仍然不完全可用。我认为这是操作系统的问题,因为它仍将文件的某些部分保存在未完全写入磁盘的缓存中,并且该工具只是自行退出而没有刷新其文件句柄。

考虑到这一点,您应该在日志中看到外部工具创建了文件、退出并在文件将被刷新/关闭之后(由操作系统 [当您在日志中找到这一点时可能删除任何过滤器] )。

因此,如果我的假设是正确的,那么根本原因将是您无法更改的外部工具的不良行为,从而导致在进程退出后稍等片刻,并希望超时时间足够长以获取文件被操作系统刷新/关闭(可能会尝试在循环中打开文件并超时直到成功)。

【讨论】:

【参考方案7】:

我建议你尝试这种方式:

process.Start();

while (!process.HasExited)

    // Discard cached information about the process.
    process.Refresh();

    // Just a little check!
    Console.WriteLine("Physical Memory Usage: " + process.WorkingSet64.ToString());

    Thread.Sleep(500);


foreach (Process current in Process.GetProcessesByName("testprogram"))

    if ((current.Id == process.Id) && !current.HasExited)
        throw new Exception("Oh oh!");

无论如何...在 HasExited 的 MSDN 页面中,我正在阅读以下突出显示的注释:

当标准输出被重定向到异步事件时 处理程序,输出处理可能没有 当此属性返回 true 时完成。确保异步 事件处理已完成,调用 WaitForExit() 重载 在检查 HasExited 之前不接受任何参数。

当您重定向所有内容时,这可能与您的问题有关。

【讨论】:

【参考方案8】:

也许问题出在测试程序中?此代码是否很好地刷新/关闭等?在我看来,如果 testprogram 将文件写入磁盘,该文件至少应该可用(是否为空)

【讨论】:

【参考方案9】:

根据MSDN documentation 为HasExited

如果一个句柄对进程开放, 操作系统释放 当进程有进程内存时 退出,但保留行政 有关过程的信息,例如 句柄、退出代码和退出时间。

可能不相关,但值得注意。

如果只是 1/10 的时间出现问题,并且该过程无论如何都会在一秒钟后消失,这取决于您对 HasExited 的使用情况,请尝试在 HasExited 检查工作后添加另一个延迟,例如

while (!process.HasExited)
    DoStuff();
Thread.Sleep(500);
Cleanup();

看看问题是否仍然存在。

就个人而言,我一直只使用Exited 事件处理程序而不是任何类型的轮询,以及围绕System.Diagnostics.Process 的简单自定义包装器来处理线程安全等问题,包装对CloseMainWindow() 的调用,然后是@ 987654327@,最后是Kill(),记录等等,从来没有遇到过问题。

【讨论】:

【参考方案10】:

有两种可能,进程对象继续持有对进程的引用,所以它已经退出,但还没有被删除。或者您正在运行该进程的第二个实例。您还应该比较进程 ID 以确保。试试这个。

    ....

    // Start the program
    process.Start();


    while (!process.HasExited)
        Thread.Sleep( 500 );

    Process[] p = Process.GetProcessesByName( "testprogram" );
    if ( p.Length != 0 && p[0].Id == process.id && ! p[0].HasExited)
        throw new Exception("Oh oh");

【讨论】:

我只运行了一个实例,我尝试保持 Windows 任务管理器打开并查看进程列表(同时检查 ID,它们匹配),实际上,当我的代码抛出异常时进程仍在列表中。有时它在抛出异常时几乎立即退出,有时可能需要一两秒钟才能消失。我不确定是否可以信任任务管理器(可能是延迟?),但事实是我的代码抛出异常 - 不是每次,而是大约 1/10 的运行。 一旦 process.HasExited 为真,则进程 退出。它死了。它可能会在一段时间内成为僵尸,尤其是当进程对象有打开的句柄时。 GetProcessByName() 有时可能会返回该进程这一事实意味着该进程仍然是僵尸进程。您应该process.Dispose() 以确保释放该句柄。但是您使用 GetProcessByName 的“测试”根本不是一件合理的事情。 process.HasExited 是您可以进行的唯一值得信赖的测试,而且您已经通过了。 听起来很合理。但是我不明白为什么文件'testprogram'写入(在它关闭之前执行),当 HasExited 为真时没有写入磁盘。如果进程是僵尸进程怎么会这样(那么它应该已将所有内容刷新到磁盘,对吗?)。据我所知,处理进程对象对进程运行的程序没有影响,这有什么帮助?【参考方案11】:

首先,使用 Process.WaitForExit 而不是轮询它是否存在问题?

无论如何,从技术上讲,该进程从可用的角度退出是可能的,但该进程在执行诸如刷新磁盘缓存之类的操作时仍会短暂存在。日志文件是否特别大(或者它在磁盘写入上执行的任何操作)?

【讨论】:

日志文件的绝对最大大小约为 1mb。我使用轮询的原因是我需要能够随时中止等待任务,并且使用 WaitForExit 至少在超时之前我会被阻塞。在您看来,即使 HasExited 返回 true,程序确实有可能将其流刷新到磁盘?我只是觉得奇怪的是,当 HasExited 为真时,该进程并未完全死/消失在进程列表中。据我所知,它没有在文档中提及任何特殊情况。 这是我以前见过的东西,但不适用于那么小的尺寸。正如另一位评论者指出的那样,尽管 Process 类将维护对进程的引用,但您可能希望在检查 Exit 之后但在尝试在进程列表中找到它之前对 Process 实例调用 Dispose。

以上是关于即使进程正在运行,Process.HasExited 也会返回 true?的主要内容,如果未能解决你的问题,请参考以下文章

Julia:即使被要求运行多个进程,spawnat 也始终在相同的线程上运行

Python multiprocessing:在退出父进程后运行进程

即使进程正常运行,如何创建核心转储? [复制]

golang exec 在正在运行的二进制/进程上执行命令

进程的状态与转换

强制删除文件,即使它正在被其他进程使用