在调用`Invoke`时避免`ObjectDisposedException`

Sae*_*ani 5 .net c# multithreading invoke winforms

我有两种形式,一种是MainForm第二种形式DebugForm.MainForm有一个按钮,用于设置和显示DebugForm,并将引用传递给已打开的SerialPort:

private DebugForm DebugForm; //Field
private void menuToolsDebugger_Click(object sender, EventArgs e)
{
    if (DebugForm != null)
    {
        DebugForm.BringToFront();
        return;
    }

    DebugForm = new DebugForm(Connection);

    DebugForm.Closed += delegate
    {
        WindowState = FormWindowState.Normal;
        DebugForm = null;
    };

    DebugForm.Show();
}
Run Code Online (Sandbox Code Playgroud)

在DebugForm中,我附加了一个方法来处理DataReceivedserialport连接的事件(在DebugForm的构造函数中):

public DebugForm(SerialPort connection)
{
    InitializeComponent();
    Connection = connection;
    Connection.DataReceived += Connection_DataReceived;
}
Run Code Online (Sandbox Code Playgroud)

然后在该Connection_DataReceived方法中,我更新DebugForm中的TextBox,即使用Invoke进行更新:

private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
{           
    _buffer = Connection.ReadExisting();
    Invoke(new EventHandler(AddReceivedPacketToTextBox));
}
Run Code Online (Sandbox Code Playgroud)

但我有一个问题.当我关闭DebugForm,它抛出一个ObjectDisposedExceptionInvoke(new EventHandler(AddReceivedPacketToTextBox));线.

我怎样才能解决这个问题?欢迎任何提示/帮助!

UPDATE

我发现如果在按钮事件中单击删除事件,并在该按钮单击中关闭表单,一切都很好,我的调试表关闭,没有任何异常......多么奇怪!

private void button1_Click(object sender, EventArgs e)
{
    Connection.DataReceived -= Connection_DebugDataReceived;
    this.Close();
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*ane 6

关闭表单会释放Form对象,但不能强制删除其他类对其进行的引用.当您为事件注册表单时,您基本上是将表单对象引用到事件源(SerialPort在本例中为实例).

这意味着,即使您的表单已关闭,事件源(您的SerialPort对象)仍在向表单实例发送事件,并且仍在运行处理这些事件的代码.问题是,当此代码尝试更新已处理的表单(设置其标题,更新其控件,调用Invoke&c.)时,您将收到此异常.

因此,您需要做的是确保在表单关闭时取消注册事件.这就像检测表单正在关闭并取消注册Connection_DataReceived事件处理程序一样简单.您可以通过覆盖OnFormClosing方法并在其中取消注册事件来检测表单是否正在关闭:

protected override OnFormClosing(FormClosingEventArgs args)
{
    Connection.DataReceived -= Connection_DataReceived;
}
Run Code Online (Sandbox Code Playgroud)

我还建议将事件注册移动到OnLoad方法的覆盖,否则它可能在表单完全构造之前接收事件,这可能导致混乱的异常.


Sae*_*ani 0

这个问题可以通过添加一个定时器来解决:

  bool formClosing = false;
    private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
      if (formClosing) return;
      _buffer = Connection.ReadExisting();
      Invoke(new EventHandler(AddReceivedPacketToTextBox));
    }
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
      base.OnFormClosing(e);
      if (formClosing) return;
      e.Cancel = true;
      Timer tmr = new Timer();
      tmr.Tick += Tmr_Tick;
      tmr.Start();
      formClosing = true;
    }
    void Tmr_Tick(object sender, EventArgs e)
    {
      ((Timer)sender).Stop();
      this.Close();
    }
Run Code Online (Sandbox Code Playgroud)

感谢MSDN的 JohnWein