SerialPort.GetPortNames() 导致 ContextSwitchDeadlock 异常,为什么?
SerialPort.GetPortNames() causing ContextSwitchDeadlock Exception, why?
我正在开发一个多线程串行端口通信软件,我注意到在使用下面提供的代码时抛出 ContextSwitchDeadlock
。
请注意,将 GetPortNames()
附加到它自己的函数,然后将每个线程(开始和结束)的 ID 写到控制台表明每个启动的线程也是正确的,如显式示例所示.
简单示例:
while (true)
{
Task.Run(() => SerialPort.GetPortNames());
Thread.Sleep(100);
}
明确的例子:
[STAThread]
private static void Main(string[] args)
{
while (true)
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
Task.Run(() => Example());
Thread.Sleep(100);
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
}
private static void Example()
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
SerialPort.GetPortNames();
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
字符串比较示例(无一例外):
[STAThread]
private static void Main(string[] args)
{
while (true)
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
Task.Run(() => Example());
Thread.Sleep(100);
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
}
private static void Example()
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
string.Compare("a", "b");
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
因为我认为没有任何线程被死锁,所以这个错误不应该发生。完整的错误信息:
Managed Debugging Assistant 'ContextSwitchDeadlock' : 'The CLR has been unable to transition from COM context 0xb434c0 to COM context 0xb43408 for 60 seconds. The thread that owns the destination context/apartment is most likely either doing a non pumping wait or processing a very long running operation without pumping Windows messages. This situation generally has a negative performance impact and may even lead to the application becoming non responsive or memory usage accumulating continually over time. To avoid this problem, all single threaded apartment (STA) threads should use pumping wait primitives (such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.
我很想知道为什么会这样。
提供的简单示例显示了实现此目的的最简单方法,而显式示例显示了跟踪所有线程的方法,其输出为:
Start 1
Start 3
End 3
End 1
Start 1
Start 3
End 3
End 1
Start 1
Start 4
End 4
End 1
Start 1
Start 3
End 3
End 1
因此可以看出没有线程死锁,因为它们都正确地开始和结束(尽管我不知道 2 号线程在哪里)。
您的主线程有一个 [STAThread]
属性。
As this answer explains,因此您的线程应该发送 COM 消息队列。但是,您的主线程无法访问消息队列(因为它只是在一个简单的控制台应用程序中),并且它陷入了无限循环,因此无论如何它都不会抽取任何东西!
控制台应用程序使用 [STAThread]
非常不寻常。如果您没有明确地对 COM 执行任何操作(即没有添加它的具体原因),您可能可以安全地删除它。如果您 出于某种原因需要使用 [STAThread]
,那么您将需要稍微重新考虑您的线程模型。
至于为什么 ContextSwitchException
仅在您调用 SerialPort 时发生,我的猜测是 SerialPort 正在调用 winapi,它最终会调用一些东西来检查您的线程是否正在抽取消息队列。如果您好奇,可以使用调试器进一步深入研究。
我正在开发一个多线程串行端口通信软件,我注意到在使用下面提供的代码时抛出 ContextSwitchDeadlock
。
请注意,将 GetPortNames()
附加到它自己的函数,然后将每个线程(开始和结束)的 ID 写到控制台表明每个启动的线程也是正确的,如显式示例所示.
简单示例:
while (true)
{
Task.Run(() => SerialPort.GetPortNames());
Thread.Sleep(100);
}
明确的例子:
[STAThread]
private static void Main(string[] args)
{
while (true)
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
Task.Run(() => Example());
Thread.Sleep(100);
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
}
private static void Example()
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
SerialPort.GetPortNames();
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
字符串比较示例(无一例外):
[STAThread]
private static void Main(string[] args)
{
while (true)
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
Task.Run(() => Example());
Thread.Sleep(100);
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
}
private static void Example()
{
Console.WriteLine("Start " + Thread.CurrentThread.ManagedThreadId);
string.Compare("a", "b");
Console.WriteLine("End " + Thread.CurrentThread.ManagedThreadId);
}
因为我认为没有任何线程被死锁,所以这个错误不应该发生。完整的错误信息:
Managed Debugging Assistant 'ContextSwitchDeadlock' : 'The CLR has been unable to transition from COM context 0xb434c0 to COM context 0xb43408 for 60 seconds. The thread that owns the destination context/apartment is most likely either doing a non pumping wait or processing a very long running operation without pumping Windows messages. This situation generally has a negative performance impact and may even lead to the application becoming non responsive or memory usage accumulating continually over time. To avoid this problem, all single threaded apartment (STA) threads should use pumping wait primitives (such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.
我很想知道为什么会这样。
提供的简单示例显示了实现此目的的最简单方法,而显式示例显示了跟踪所有线程的方法,其输出为:
Start 1
Start 3
End 3
End 1
Start 1
Start 3
End 3
End 1
Start 1
Start 4
End 4
End 1
Start 1
Start 3
End 3
End 1
因此可以看出没有线程死锁,因为它们都正确地开始和结束(尽管我不知道 2 号线程在哪里)。
您的主线程有一个 [STAThread]
属性。
As this answer explains,因此您的线程应该发送 COM 消息队列。但是,您的主线程无法访问消息队列(因为它只是在一个简单的控制台应用程序中),并且它陷入了无限循环,因此无论如何它都不会抽取任何东西!
控制台应用程序使用 [STAThread]
非常不寻常。如果您没有明确地对 COM 执行任何操作(即没有添加它的具体原因),您可能可以安全地删除它。如果您 出于某种原因需要使用 [STAThread]
,那么您将需要稍微重新考虑您的线程模型。
至于为什么 ContextSwitchException
仅在您调用 SerialPort 时发生,我的猜测是 SerialPort 正在调用 winapi,它最终会调用一些东西来检查您的线程是否正在抽取消息队列。如果您好奇,可以使用调试器进一步深入研究。