【问题标题】:Process.HasExited returns true even though process is running?即使进程正在运行,Process.HasExited 也会返回 true?
【发布时间】: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)吗?

标签: c# process exit terminate


【解决方案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 documentationHasExited

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

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

                如果只是 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。
                  猜你喜欢
                  • 1970-01-01
                  • 2011-02-16
                  • 1970-01-01
                  • 2012-10-10
                  • 2012-01-14
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-08-09
                  • 2020-06-03
                  相关资源
                  最近更新 更多