c# - Process.Start() 在后台线程上运行时挂起

标签 c# multithreading timer

我整天都在进行故障排除。做了一些之后 research以及大量的试验和错误,似乎我已经能够将问题缩小到我对 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();
}

最佳答案

关于这个问题有很多重复的问题,没有一个完全适合你的情况。您可以使用调试器的 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() 方法调用引起的。现在它获得了一个锁,以防止其他线程干扰控制台。这是整个 Microsoft 软件的一个相当全局性的变化,在 VS2012 创建的 C/C++ 应用程序中使用的 CRT 也添加了这个锁。确切的原因对我来说不是那么清楚,但是当你的程序要求输入时,肯定必须对控制台输出做一些事情,而不是与控制台输入混合在一起。确切地说,为什么 InputEncoding 属性也需要获取该锁有点难以解释,但符合对控制台输入进行序列化访问的模式。这当然让许多程序员大吃一惊,尤其是那些像您一样编写测试线程代码的小测试应用程序的程序员。 TDD 有点挫折。

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


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

关于c# - Process.Start() 在后台线程上运行时挂起,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16202678/

相关文章:

c# - C# 的替代加密安全伪随机数生成器

c# - Entity Framework 似乎在缓存数据

c++ - 如何以新方式编写线程安全的 OpenSSL 代码?

javascript - 自 Chrome 88 以来的 SignalR 和/或计时器问题

c# - 使用计时器在visual c#中每秒调用一个方法

c# - 为 Windows Store App 定制的虚拟键盘

c# - 获取 C# 字符串的 NON FIRST 段的快速而巧妙的方法

c++ - 以线程安全方式每秒打印当前队列元素数

c++ - 多线程系统中不 protected get 函数 - 需要 volatile 吗?

c# - 大量定时器