65.9K
CodeProject 正在变化。 阅读更多。
Home

Process Async Reader Bug

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2007年12月15日

CPOL

2分钟阅读

viewsIcon

25060

我发现的 Process 类中异步读取器的一个缺陷的解决方法。

问题

假设你从两个不同的线程启动相同的进程,就像这样

using System;
using System.IO;
using System.Threading;

using Clifton.Timers;

namespace Launcher
{
  class Program
  {
    enum Process
    {
      Proc1, 
      Proc2,
    }

    static void Main(string[] args)
    {
      Thread t1 = new Thread(new ThreadStart(Launch1));
      Thread t2 = new Thread(new ThreadStart(Launch2));
      t1.Start();
      t2.Start();
      t1.Join();
      t2.Join();
    }

    static void Launch1()
    {
      DebugStopwatch.Start(Process.Proc1);
      LaunchAndWait(5000, "P1");
      DebugStopwatch.Stop(Process.Proc1);
      long t = 0;
      DebugStopwatch.ElapsedMilliseconds(Process.Proc1, ref t);
      Console.WriteLine("P1 took " + t + " ms");
    }

    static void Launch2()
    {
      DebugStopwatch.Start(Process.Proc2);
      LaunchAndWait(10000, "P2");
      DebugStopwatch.Stop(Process.Proc2);
      long t = 0;
      DebugStopwatch.ElapsedMilliseconds(Process.Proc2, ref t);
      Console.WriteLine("P2 took " + t + " ms");
    }

    static void LaunchAndWait(int ms, string procName)
    {
      Executable exec = new Executable(Path.GetFullPath(
         "..\\..\\..\\TestProcess\\bin\\debug\\TestProcess.exe"), ms.ToString());
      exec.Start();
      exec.WaitForExitInfinite();
      Console.WriteLine(procName + " done.");
    }
  }
}

LaunchAndWait 方法调用 WaitForExitInfinite,其代码如下

public void WaitForExitInfinite()
{
  exe.WaitForExit();
}

其中 exe 是 .NET Process 类的一个实例。

该进程所做的仅仅是休眠在命令行参数中指定的时间。

using System;
using System.Threading;

namespace TestProcess
{
  class Program
  {
    static void Main(string[] args)
    {
      Thread.Sleep(Convert.ToInt32(args[0]));
    }
  }
}

因此,由于 Launch1 传入 5000,而 Launch2 传入 10000,你期望(并且正确地)计时器的结果是

P1 done.
P1 took 5091 ms
P2 done.
P2 took 10094 ms
Press any key to continue . . .

在这个例子中,Executable 类所做的仅仅是启动进程。注意注释掉的开始异步输出和错误读取器的调用。

public void Start()
{
  exe = new Process();
  exe.StartInfo.FileName = filename;
  exe.StartInfo.UseShellExecute = false;
  exe.StartInfo.RedirectStandardInput = true;
  exe.StartInfo.RedirectStandardOutput = true;
  exe.StartInfo.RedirectStandardError = true;
  exe.StartInfo.CreateNoWindow = false;
  exe.StartInfo.Arguments = arguments;
  exe.ErrorDataReceived += new DataReceivedEventHandler(MonitorError);
  exe.OutputDataReceived += new DataReceivedEventHandler(MonitorOutput);
  exe.Start();
//exe.BeginOutputReadLine();
//exe.BeginErrorReadLine();
}

现在,请看——我将取消注释上面的两行代码并重新运行测试,结果如下

P1 done.
P2 done.
P1 took 10061 ms
P2 took 10061 ms
Press any key to continue . . .

因此,**仅仅通过启用异步读取,第一个进程不会指示它已终止,直到第二个进程终止!**

解决方案

解决方案是调用带有超时时间的 Process.WaitForExit,例如 100 毫秒。因此,我将 LaunchAndWait 修改为调用 WaitForExit 而不是 WaitForExitInfinite

static void LaunchAndWait(int ms, string procName)
{
  Executable exec = new Executable(Path.GetFullPath(
       "..\\..\\..\\TestProcess\\bin\\debug\\TestProcess.exe"), ms.ToString());
  exec.Start();
  exec.WaitForExit();
  Console.WriteLine(procName + " done.");
}

WaitForExit 调用与上面的 WaitForExitInfinite 调用进行比较

public void WaitForExit()
{
  bool running = true;

  while (running)
  {
    running = !exe.WaitForExit(100); 
  }
}

这个调用虽然也等待永远,但将一个超时值传递给 Process.WaitForExit,并依赖返回值来测试进程是否已终止。使用此调用,第一个进程在 5 秒后退出,第二个进程在 10 秒后退出,正如我们期望的那样。

结论

我直到在测试一个多线程电影重新编码器时才发现这个错误,并且代码在第二个进程完成后才为第一个完成的进程分配新的任务。首先怀疑我自己的代码,我大吃一惊地发现这个问题出在异步读取器上,这对于从重新编码器进程获取反馈至关重要。我搜索过其他遇到过这个问题的用户,但我的搜索没有结果,所以我写了这篇文章。如果任何人有关于这个问题的更多信息,请发表评论!

Process 异步读取器错误 - CodeProject - 代码之家
© . All rights reserved.