【问题标题】:SerialPort.GetPortNames() causing ContextSwitchDeadlock Exception, why?SerialPort.GetPortNames() 导致 ContextSwitchDeadlock 异常,为什么?
【发布时间】:2019-08-01 11:14:26
【问题描述】:

我正在开发一个多线程串行端口通信软件,我注意到在使用下面提供的代码时会抛出 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);
}

由于我认为没有任何线程被死锁,因此不应发生此错误。完整的错误信息:

托管调试助手 'ContextSwitchDeadlock' : 'CLR 在 60 秒内无法从 COM 上下文 0xb434c0 转换到 COM 上下文 0xb43408。拥有目标上下文/单元的线程很可能要么进行非泵送等待,要么处理非常长时间运行的操作而不泵送 Windows 消息。这种情况通常会对性能产生负面影响,甚至可能导致应用程序变得无响应或内存使用量随着时间的推移不断累积。为避免此问题,所有单线程单元 (STA) 线程都应使用泵送等待原语(例如 CoWaitForMultipleHandles)并在长时间运行的操作期间定期泵送消息。

我很想知道为什么会这样。

提供的简单示例显示了实现此目的的最简单方法,而显式示例显示了跟踪所有线程的方法,其输出为:

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]。为什么?这是控制台应用程序还是 GUI 应用程序?
  • 静态方法通常是线程安全的,我看不出source有什么特别之处,所以和串口无关。
  • 我认为这是你的主线程。 STA 线程应该会泵送消息队列,但您的线程没有这样做,因为 1)它是一个无法访问消息队列的控制台应用程序,以及 2)它正在执行无限循环。
  • @canton7 说得通。我启用了 ContextSwitchException,但它没有给我太多信息,因为它在“应用程序处于中断模式”页面上停止。如果您将评论作为答案发布,我可以选择它作为帖子的正确答案,以便其他人可以更快地看到答案。

标签: c# multithreading serial-port task


【解决方案1】:

您的主线程上有一个[STAThread] 属性。

As this answer explains,因此您的线程应该会抽取 COM 消息队列。但是,您的主线程无法访问消息队列(因为它只是在一个简单的控制台应用程序中),并且它陷入了无限循环,因此无论如何它都不会抽任何东西!

控制台应用程序使用[STAThread] 是非常不寻常的。如果您没有明确地对 COM 做任何事情(即没有具体原因添加它),您可能可以安全地删除它。如果您确实出于某种原因需要使用[STAThread],那么您需要重新考虑一下您的线程模型。

至于为什么ContextSwitchException 仅在您调用 SerialPort 时出现,我的猜测是 SerialPort 正在调用 winapi,它最终会调用一些东西来检查您的线程是否正在泵送消息队列。如果您有兴趣,可以使用调试器进一步深入研究。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    相关资源
    最近更新 更多