【问题标题】:Deadlock when worker thread tries to invoke something on main thread工作线程尝试在主线程上调用某些内容时出现死锁
【发布时间】:2012-07-19 09:35:38
【问题描述】:

我正在使用一个外部组件,它定期从工作线程中拍摄事件。在我的事件处理程序中,我使用 Dispatcher 在主线程上调用某些方法。这很好用...

private void HandleXYZ(object sender, EventArgs e)
{
    ...
    if(OnTrigger != null)
        dispatcher.Invoke(OnTrigger, new TimeSpan(0, 0, 1), e);
}

但是,当程序关闭并且外部组件 Dispose()s 时,程序有时会挂起(并且只能在任务管理器中看到和杀死)。

当我查看正在发生的事情时,看起来“组件”正在等待事件在主线程上返回(它停留在 Dispose() 方法中),而工作线程等待调度程序调用提到对主线程的调用(它挂在 dispatcher.Invoke-line 中)。

现在我通过在 Invoke 中添加超时来解决关机问题,这似乎有效但感觉不对。 有没有更清洁的方法来做这样的事情?我可以强制主线程在关闭之前为其他线程的作业花费一些时间吗?

我试图在关闭之前“断开”事件,但这无济于事,因为调度程序(可能)已经在等待,当程序开始关闭时......

PS:外部组件在这里表示我无权访问源代码...

【问题讨论】:

  • 请下次使用段落
  • 粘贴代码会有很大帮助

标签: c# multithreading deadlock


【解决方案1】:

是的,这是死锁的常见来源。它挂起是因为调度程序退出了调度程序循环,它将不再响应 Invoke 请求。一个快速的解决方法是改用 BeginInvoke,它不会等待调用目标完成执行。另一个快速方法是将工作线程的 IsBackground 属性设置为 True,以便 CLR 将其杀死。

这些是快速修复,它们可能对您有用。当然在你的开发机器上,但是如果你有一种唠叨的感觉,它可能仍然会出错,那么你是对的,没有观察到死锁或线程竞争并证明它们不存在。有两种“好”的方法可以完全安全地做到这一点:

  • 在您确定工作线程终止并且不能再引发事件之前,不要让主线程退出。 This answer 显示模式。

  • 使用 Environment.Exit() 强制终止程序。这是非常粗略但非常有效的大锤,只有当你有一个UI线程只是第二公民的线程繁重的程序时,你才能伸手去拿。奇怪的是,这听起来像是一种合适的方法,但新的 C++ 语言标准已将其提升为受支持的终止程序的方式。您可以在this answer 中阅读有关它的更多信息。请注意它是如何允许注册清理功能的,您必须对 AppDomain.ProcessExit 事件执行类似的操作。在执行此操作之前,请先关注第一个项目符号。

【讨论】:

  • 好答案!我会说 BeginInvoke 方式比超时感觉更好。
  • 这是否意味着:如果程序没有关闭,并且主线程将等待事件......调度程序是否会将 Invoke 传递给主线程,尽管主线程是“积极”等待?
  • 不确定这与原始问题有何关系。但是主线程中的任何“等待”也可能导致死锁。只有在主线程空闲并执行调度程序循环时才能调度 Invoke() 调用。
  • 谢谢! (这个问题主要与你的回答有关......但你的回答告诉我,关闭不一定会在僵局中发挥作用 - 很高兴知道)
  • 我使用大锤已经 30 年了。在您确定工作线程终止之前不允许主线程退出通常是困难的、不切实际的或实际上是不可能的。 “新的 C++ 语言标准已将其提升为终止程序的受支持方式”——这是因为它是唯一肯定有效的方式——只有操作系统具有终止任何内核上任何状态的线程的工具。除非绝对必须,否则我不会使用 Invoke() 之类的同步通信,也不会在应用关闭时等待任何可能阻止调用 ExitProcess() 的操作。
【解决方案2】:

对于事件订阅,当您知道不再需要某个特定对象时,清理它们确实是个好主意。否则,您将面临造成内存泄漏的风险。您可能还想看看weak event pattern (MSDN)。

关于死锁本身,不知道你的代码,我们只能猜测。

我不认为HandleXYZ() 是罪魁祸首,我宁愿检查您的IDisposable() 实施。查看MSDN documentation 并将其与您的实现进行比较。

我想在您的实现中的某个地方进行了一些方法调用,这些方法调用取决于GarbageCollector 的时间,这是不确定的:有时它可能会在您的情况下起作用,有时可能不会。

【讨论】:

  • 谢谢...但不幸的是,我无法使用 Dispose() 的代码(外部组件)
  • 问题是,@FrankB,你做任何清理工作吗?作为一般规则,总是清理任何实现IDispoeable() 的类。如果该第三方组件正在等待某些东西,很可能是您自己的代码没有正确释放某些资源。
  • 其他组件等待事件返回,而我的事件处理程序等待主线程(被等待组件阻塞......)。只有当我可以强制 Dispatcher 停止等待主线程时,清理才是解决方案。除了设置超时,我还能这样做吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多