2012-01-28 75 views
6

我遇到了C#中的多线程问题。 我使用事件从另一个线程更新表单中的标签,我当然需要使用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); 
} 

我希望在这里任何人有任何的想法有什么不对这个代码。 提前谢谢!

+0

可在实际应用中的更新调用导致窗口处置?在这种情况下,后台线程可能会有一个锁,并且UI线程可能会在Dispose锁定在后台线程所持的相同对象上。 – 2012-01-28 14:39:39

+1

你从哪里得到变量InvokeRequired,它应该在你想要更新的控件上调用,即:if(label.InvokeRequired){//} – Lloyd 2012-01-28 15:01:44

回答

1

我真的会在这里简单。我不会执行棘手的线程安全代码,而只会捕获异常,如果失败则什么也不做。

假设这是一个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. 
} 

更简单,非常简单。如果评论指定为什么你发现异常,我认为没关系。

+1

坏主意IMO ... – CodesInChaos 2012-01-28 14:55:47

+0

@CodeInChaos我不认为锁的复杂性,Dispose的重写等。比简单地捕捉异常要好。也许你可以解释**为什么**你认为这是一个坏主意...... – ken2k 2012-01-28 14:58:56

+0

相同的观点在这里:http://stackoverflow.com/a/1874785/870604 – ken2k 2012-01-28 15:00:15

6

你没有考虑到的是,委托传递给Invoke在UI线程上被异步调用。调用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    
+1

Invoke调用总是失败。在调用Control的精确时间调用Invoke时,代码仍然会导致发生无法解析的异常 – JaredPar 2012-01-28 15:56:13

+0

Invoke始终是同步的。 – usr 2012-01-28 16:08:48

+0

@usr我想描述的是,Invoke与UI线程是异步的。它与后台线程同步。我会尽量让我的答案更清楚 – shf301 2012-01-28 17:14:01

0

IMO Dispose为时已晚......

我建议把一些代码为FormClosingDispose发生AFAIK之前被调用。

对于这种情况,我通常倾向于为您的支票使用不同的(原子)模式 - 例如,通过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)

0

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

1

一个使用Control.Invoke的危险是,它可以在一个不幸的时间设置在UI线程上你建议。发生这种情况最常见的方式是当你有事件

  1. 后台线程的顺序如下:排队一个电话回来调用
  2. 前台线程:配置的控制上,要求调用
  3. 前台线程背景:在出售的控件上退出回调

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

尽管这会导致死锁,但新代码仍然存在。代码将在步骤#1中进行锁定。然后处理在步骤#2发生在用户界面中,并且它正在等待锁定,该锁定在步骤#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"; 
} 
1

你为什么不只是使用的BeginInvoke,而不是调用 - 这亿韩元不会阻止后台线程。它看起来并不像有就是后台线程需要等待从你表现出

1

调用Dispatcher.Invoke(在 一个WPF应用程序时,另一种死锁的情况出现要发生的UI更新任何具体原因)或Control.Invoke(在Windows窗体应用程序中) ,同时拥有一个锁。如果UI碰巧运行的另一个 方法在等待对同一个锁,死锁会发生右 那里。这通常可以通过调用BeginInvoke而不是Invoke的 来解决。另外,您也可以在调用 调用之前释放你的锁,不过如果你的来电者拿出锁这是不行的。我们 解释调用而BeginInvoke在富客户端应用和线程 亲和力。

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