‘死锁’只有一个被锁定的物体?
我在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线程上。 最常见的方式是当您具有以下事件顺序时
- 后台线程:使用Invoke对回调进行排队
- 前台线程:释放控件,其背景名为Invoke
- 前台线程:将已回拨的控件上的呼叫列出
在这种情况下,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)
。
上述就是C#学习教程:‘死锁’只有一个被锁定的物体?分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/989147.html