‘死锁’只有一个被锁定的物体?

我在C#中遇到multithreading问题。 我使用一个事件来更新另一个线程的表单中的标签,当然我需要使用Invoke()命令。 那部分也很好。 但是,用户可以关闭表单,如果在不幸的时间发送事件,程序可能会崩溃。

所以,我想我会简单地覆盖窗体的Dispose()方法,在锁定代码中将布尔值设置为true,并检查布尔值并在锁定代码中调用事件。

但是,每次关闭表单时,程序都会完全冻结。

以下是代码中提到的部分:

private object dispose_lock = new object(); private bool _disposed = false; private void update(object sender, EventArgs e) { if (InvokeRequired) { EventHandler handler = new EventHandler(update); lock (dispose_lock) { if (_disposed) return; Invoke(handler); // this is where it crashes without using the lock } return; } label.Text = "blah"; } protected override void Dispose(bool disposing) { eventfullObject.OnUpdate -= update; lock (dispose_lock) // this is where it seems to freeze { _disposed = true; // this is never called } base.Dispose(disposing); } 

我希望这里的任何人都知道这段代码有什么问题。 先感谢您!

我真的很简单。 而不是实现棘手的线程安全代码,我只是捕获exception,如果失败则什么都不做。

假设它是ObjectDisposedException

 try { this.Invoke(Invoke(handler)); } catch (ObjectDisposedException) { // Won't do anything here as // the object is not in the good state (diposed when closed) // so we can't invoke. } 

它更简单,更直接。 如果注释指定捕获exception的原因,我认为没关系。

您没有考虑的是传递给Invoke委托在UI线程上异步Invoke 。 调用Invoke将消息发布到表单消息队列,并在以后的某个时间被提取。

发生的事情不是:

 UI Thread Background Thread Call update() take lock Call Invoke() Call update() release lock Call Dispose() take lock release lock 

但反而:

 UI Thread Background Thread Call update() take lock Call Invoke() block until UI Thread processes the message Process messages ... Dispose() wait for lock ****** Deadlock! ***** ... Call update() release lock 

因此,后台线程可以在UI线程尝试运行Dispose保持锁定

解决方案比您尝试的简单得多。 由于Invoke是异步发布的,因此不需要锁定。

 private bool _disposed = false; private void update(object sender, EventArgs e) { if (InvokeRequired) { EventHandler handler = new EventHandler(update); Invoke(handler); return; } if (_disposed) return; label.Text = "blah"; } protected override void Dispose(bool disposing) { eventfullObject.OnUpdate -= update; _disposed = true; // this is never called base.Dispose(disposing); } 

_disposed标志仅在UI线程上读取或写入,因此不需要锁定。 现在你调用堆栈看起来像:

 UI Thread Background Thread Call update() take lock Call Invoke() block until UI Thread processes the message Process messages ... Dispose() _disposed = true; ... Call update() _disposed is true so do nothing 

使用Control.Invoke一个危险是它可以在你建议的不幸时间处理在UI线程上。 最常见的方式是当您具有以下事件顺序时

  1. 后台线程:使用Invoke对回调进行排队
  2. 前台线程:释放控件,其背景名为Invoke
  3. 前台线程:将已回拨的控件上的呼叫列出

在这种情况下,Invoke将失败并导致在后台线程上引发exception。 这可能是导致您的应用程序崩溃的原因。

使用新代码虽然这会导致死锁。 代码将在步骤#1中进行锁定。 然后在步骤#2的UI中发生处置,它正在等待锁定,直到步骤#3完成后才会释放锁定。

处理此问题的最简单方法是接受Invoke是一个可以并且将失败的操作,因此需要try / catch

 private void update(object sender, EventArgs e) { if (InvokeRequired) { EventHandler handler = new EventHandler(update); try { Invoke(handler); } catch (Exception) { // Control disposed while invoking. Nothing to do } return; } label.Text = "blah"; } 

为什么不使用BeginInvoke而不是Invoke – 这不会阻止后台线程。 看起来没有任何特定原因,后台线程需要等待UI更新从您显示的内容发生

在拥有锁时调用Dispatcher.Invoke(在WPF应用程序中)或Control.Invoke(在Windows窗体应用程序中)时会出现另一种死锁情况。 如果UI碰巧正在运行另一个等待同一个锁的方法,那么就会发生死锁。 这通常可以通过调用BeginInvoke而不是Invoke来解决。 或者,您可以在调用Invoke之前释放锁定,但如果调用者取消锁定,则无法执行此操作。 我们在Rich Client Applications和Thread Affinity中解释Invoke和BeginInvoke。

来源: http : //www.albahari.com/threading/part2.aspx

只是因为没有其他答案是罪魁祸首,是否有其他代码终止未发布的线程? 我在想你可能正在使用普通的Threads而不是BackgroundWorker,可能忘记将Thread.isBackround设置为true

IMO Dispose为时已晚……

我建议将一些代码放入FormClosing ,在Dispose发生AFAIK之前FormClosing它。

对于这种情况,我通常倾向于使用不同的(primefaces)模式进行检查 – 例如通过Interlocked类。

 private long _runnable = 1; private void update(object sender, EventArgs e) { if (InvokeRequired) { EventHandler handler = new EventHandler(update); if (Interlocked.Read (ref _runnable) == 1) Invoke(handler); return; } label.Text = "blah"; } 

FormClosing您只需调用Interlocked.Increment (ref _runnable)