Process Async Reader Bug






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