5

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

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()在程序结束之前完成。

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();
}
4

1 回答 1

9

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

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 类使用它来确定需要使用什么编码来将进程的重定向输出转换为字符串。

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

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


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

于 2013-04-25T15:10:58.767 回答