我整天都在进行故障排除。在做了一些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();
}
答案 0 :(得分:8)
关于这个问题有很多重复的问题,没有一个完全适合你的情况。您可以使用调试器的Debug + Windows + Threads窗口来查看问题。找到计时器线程并双击它。查看Call Stack窗口以查看:
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 Update以获取它。