Process.Start() 在后台线程上运行时挂起 [英] Process.Start() hangs when running on a background thread

查看:25
本文介绍了Process.Start() 在后台线程上运行时挂起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一整天都在排查问题.在做了一些研究和大量的试错之后,似乎我已经能够将问题缩小到我对 process.Start() 的调用在计时器线程上不起作用的事实.下面的代码在主线程上运行时有效.将完全相同的代码放在计时器回调中,它会挂起.为什么?我如何让它与计时器一起工作?

I've been troubleshooting all day. After doing some research and a lot of trial and error, it seems I've been able to narrow down the issue to the fact that my call to process.Start() doesn't work on a timer thread. The code below works when running on the main thread. Put that exact same code in a timer callback, and it hangs. Why? How do I get it to work with a timer?

private static void RunProcess()
{
    var process = new Process();

    process.StartInfo.FileName = "cmd";
    process.StartInfo.Arguments = "/c exit";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardInput = true;
    process.StartInfo.RedirectStandardOutput = true;

    process.Start();  // code hangs here, when running on background thread

    process.StandardOutput.ReadToEnd();

    process.WaitForExit();
}

编辑

作为测试,我在另一台笔记本电脑上使用了完全相同的代码,但遇到了同样的问题.这是可以粘贴到控制台应用程序中的完整代码.process.Start() 挂起,但只要我按任意键结束,process.Start() 就会在程序结束前完成.

As a test, I used this exact same code on another laptop, and I experienced the same problem. This is complete code that can be pasted into a console app. process.Start() hangs, but as soon as I hit any key to end, process.Start() completes before the program ends.

private static System.Timers.Timer _timer;
private static readonly object _locker = new object();

static void Main(string[] args)
{
    ProcessTest();

    Console.WriteLine("Press any key to end.");
    Console.ReadKey();
}
private static void ProcessTest()
{
    Initialize();
}
private static void Initialize()
{
    int timerInterval = 2000;
    _timer = new System.Timers.Timer(timerInterval);
    _timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
    _timer.Start();
}
private static void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
    if (!Monitor.TryEnter(_locker)) { return; }  // Don't let  multiple threads in here at the same time.
    try
    {
        RunProcess();
    }
    finally
    {
        Monitor.Exit(_locker);
    }
}
private static void RunProcess()
{
    var process = new Process();
    process.StartInfo.FileName = "cmd";
    process.StartInfo.Arguments = "/c exit";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardInput = true;
    process.StartInfo.RedirectStandardOutput = true;
    process.Start();  // ** HANGS HERE **
    process.StandardOutput.ReadToEnd();
    process.WaitForExit();
}

推荐答案

关于这个问题有很多重复的问题,但没有一个完全适合您的情况.您可以使用调试器的 Debug + Windows + Threads 窗口查看问题.找到计时器线程并双击它.查看调用堆栈窗口可以看到:

There are a lot of duplicate questions about this problem, none that exactly fits your case. You can see the problem by using the debugger's Debug + Windows + Threads window. Locate the timer thread and double-click it. Look at the Call Stack window to see:

mscorlib.dll!System.Console.InputEncoding.get() + 0x66 bytes    
System.dll!System.Diagnostics.Process.StartWithCreateProcess(System.Diagnostics.ProcessStartInfo startInfo) + 0x7f5 bytes   
System.dll!System.Diagnostics.Process.Start() + 0x88 bytes  
ConsoleApplication70.exe!Program.RunProcess() Line 43 + 0xa bytes   C#
ConsoleApplication70.exe!Program.OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e) Line 28 + 0x5 bytes    C#
    // etc...

线程在 Console.InputEncoding 属性 getter 上死锁.Process 类使用它来确定需要使用什么编码来将进程的重定向输出转换为字符串.

The thread is deadlocked on the Console.InputEncoding property getter. Which is used by the Process class to figure out what encoding needs to be used to translate the redirected output of the process into strings.

这是特定于 .NET 4.5 的,它也会影响在安装了 4.5 的机器上以 4.0 为目标的应用程序,因为它不是 .NET 的并行版本.死锁是由主线程中的 Console.ReadKey() 方法调用引起的.它现在获取一个锁,以防止其他线程与控制台混淆.这是微软软件的一个相当全局性的变化,VS2012 创建的 C/C++ 应用程序中使用的 CRT 也添加了这个锁.确切的原因对我来说不是很清楚,但肯定必须对控制台输出做一些事情,而不是在您的程序要求输入时与控制台输入混合.InputEncoding 属性为何也需要锁定,这有点难以解释,但符合串行化访问控制台输入的模式.这对许多程序员来说当然是一个很大的惊喜,尤其是那些像你一样编写测试线程代码的小测试应用程序的程序员.TDD 有点受挫.

This is specific to .NET 4.5, it will also affect apps that target 4.0 on a machine that has 4.5 installed since it is not a side-by-side version of .NET. The deadlock is caused by the Console.ReadKey() method call in your main thread. Which now acquires a lock that prevents other threads from messing with the console. This has been a fairly global change across Microsoft software, the CRT that is used in C/C++ apps created by VS2012 also added this lock. The exact reason isn't that clear to me, but surely has to do something with console output not getting intermingled with console input while your program is asking for input. Exactly why the InputEncoding property needs to take that lock as well is, well, a bit hard to explain but fits the pattern of serializing access to console input. This of course comes as a big surprise to many programmers, especially the ones that write little test apps that test threaded code, like you did. Bit of a setback to TDD.

解决方法有点令人不快,TDD 明智,您必须停止使用 Console.ReadKey() 以避免死锁.真正的程序会使用 AutoResetEvent 的 WaitOne() 方法来知道工作线程已完成执行.或者 CountDownEvent.Wait(),更符合尝试代码几次.等等.

The workaround is a bit unpleasant, TDD wise, you do have to stop using Console.ReadKey() to avoid the deadlock. Real programs would use the WaitOne() method of an AutoResetEvent to know that the worker thread finished executing. Or CountDownEvent.Wait(), more in keeping with trying out code a couple of times. Etcetera.

更新:此死锁场景已在 .NET 4.5 的服务更新中解决.在您的计算机上启用 Windows 更新以获取它.

UPDATE: this deadlock scenario was resolved in a service update for .NET 4.5. Enable Windows Update on your machine to get it.

这篇关于Process.Start() 在后台线程上运行时挂起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆