5

我正在使用一个外部组件,它定期从工作线程中拍摄事件。在我的事件处理程序中,我使用 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:外部组件在这里意味着我无权访问源代码...

4

2 回答 2

6

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

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

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

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

于 2012-07-19T10:14:24.723 回答
1

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

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

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

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

于 2012-07-19T10:00:21.497 回答