如何正确处理表单,而不会有从已处理对象上的另一个线程调用 Invoke 的风险?

Nik*_*vak 5 c# events multithreading dispose winforms

我有一个表单,它“监听”在其他地方引发的事件(不在表单本身,也不是它的子控件之一)。事件由即使在处理 Form 之后仍然存在的对象引发,并且可能在除创建 Form 句柄的线程之外的线程中引发,这意味着我需要在事件处理程序中执行一个 Invoke(以显示形式,例如)。

Dispose(bool)表单(覆盖)的方法中,我取消订阅了调用此方法时可能仍会订阅的所有事件。但是,有时仍会从事件处理程序之一调用 Invoke。我认为这是因为事件处理程序在取消订阅事件前片刻被调用,然后操作系统将控制权切换到执行的 dispose 方法,然后将控制权返回给对已处置对象调用 Invoke 方法的处理程序。

锁定线程无济于事,因为调用 Invoke 将锁定调用线程,直到主线程处理调用的方法。这可能永远不会发生,因为主线程本身可能正在等待释放调用调用线程已获取的对象上的锁,从而造成死锁。

因此,简而言之,当订阅可能在不同线程中引发的外部事件时,如何正确处理表单?

以下是目前一些关键方法的外观。这种方法遇到了我上面描述的问题,但我不确定如何纠正它们。

这是一个处理模型数据部分更改的事件处理程序:

private void updateData()
{
 if (model != null && model.Data != null)
 {
  model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);

  model.Data.SomeDataChanged += new MyEventHandler(updateSomeData);
 }
 updateSomeData();
}
Run Code Online (Sandbox Code Playgroud)

这是一个必须对视图进行更改的事件处理程序:

private void updateSomeData()
{
 if (this.InvokeRequired) this.myInvoke(new MethodInvoker(updateSomeData));
 else
 {
  // do the necessary changes
 }
}
Run Code Online (Sandbox Code Playgroud)

和 myInvoke 方法:

private object myInvoke(Delegate method)
{
 object res = null;
 lock (lockObject)
 {
  if (!this.IsDisposed) res = this.Invoke(method);
 }
 return res;
}
Run Code Online (Sandbox Code Playgroud)

我对Dispose(bool)方法的覆盖:

protected override void Dispose(bool disposing)
{
 lock (lockObject)
 {
  if (disposing)
  {
   if (model != null)
   {
    if (model.Data != null)
    {
     model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);
    }
    // unsubscribe other events, omitted for brevity
   }
   if (components != null)
   {
    components.Dispose();
   }
  }
  base.Dispose(disposing);
 }
}
Run Code Online (Sandbox Code Playgroud)

更新(根据艾伦的要求):

我从来没有明确调用 Dispose 方法,我让框架来完成。到目前为止,死锁仅在应用程序关闭时发生。在我进行锁定之前,我有时会在简单地关闭表单时抛出一些异常。

sup*_*cat 4

有两种方法需要考虑。一是在锁内有一个锁定对象Form,并且内部调用DisposeBeginInvoke调用发生在锁内;由于两者Dispose都不BeginInvoke应该花费很长时间,因此代码永远不必等待锁定很长时间。

另一种方法是仅声明由于Control.BeginInvoke/中的设计错误Form.BeginInvoke,这些方法有时会抛出实际上无法阻止的异常,并且在操作是否发生在表单上并不重要的情况下应该简单地被吞掉无论如何,它已经被处理掉了。