Avoiding `ObjectDisposedException` while calling `Invoke`

天涯浪子 提交于 2019-12-10 14:28:07

问题


I have 2 forms, one is MainForm and second is DebugForm. The MainForm has a button that sets up and shows the DebugForm like this, And passes a reference to an already opened 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();
}

In the DebugForm, I append a method to handle the DataReceived event of the serialport connection (in DebugForm's constructor):

public DebugForm(SerialPort connection)
{
    InitializeComponent();
    Connection = connection;
    Connection.DataReceived += Connection_DataReceived;
}

Then in the Connection_DataReceived method, I update a TextBox in the DebugForm, that is using Invoke to do the update:

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

But I have a problem. As soon as I close the DebugForm, it throws an ObjectDisposedException on the Invoke(new EventHandler(AddReceivedPacketToTextBox)); Line.

How can I fix this? Any tips/helps are welcome!

UPDATE

I found out if I remove the event in a button event click , and close the form in that button click, everything is fine and my debugform gets closed without any exception...how odd!

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

回答1:


Closing a form disposes of the Form object but cannot forcibly remove references that other classes have to it. When you register your form for events, you are basically giving a reference to your form object to the source of the events (the SerialPort instance in this case).

This means that, even though your form is closed, the event source (your SerialPort object) is still sending events to the form instance and the code to handle these events is still being run. The problem then is that when this code tries to update the disposed form (set its title, update its controls, call Invoke, &c.) you will get this exception.

So what you need to do is ensure that the event gets deregistered when your form closes. This is as simple as detecting that the form is closing and unregister the Connection_DataReceived event handler. Handily you can detect the form is closing by overriding the OnFormClosing method and unregistering the event in there:

protected override OnFormClosing(FormClosingEventArgs args)
{
    Connection.DataReceived -= Connection_DataReceived;
}

I would also recommend moving the event registration to an override of the OnLoad method as otherwise it may receive events before the form has been fully constructed which could cause confusing exceptions.




回答2:


You haven't shown the code for the AddReceivedPacketToTextBox method.

You could try checking for a disposed form in that method:

private void AddReceivedPacketToTextBox(object sender, EventArgs e)
{
    if (this.IsDisposed) return;

    ...
}

Detaching the DataReceived event handler when closing the form is probably a good idea, but isn't sufficient: there is still a race condition which means your AddReceivedPacketToTextBox can be called after the form is closed/disposed. The sequence would be something like:

  • Worker thread: DataReceived event fired, Connection_DataReceived starts executing
  • UI thread: Form closed and disposed, DataReceived event detached.
  • Worker thread: calls Invoke
  • UI thread: AddReceivedPacketToTextBox executed while form is disposed.

I found out if I remove the event in a button event click , and close the form in that button click, everything is fine and my debugform gets closed without any exception...how odd!

That's not odd. Multithreading bugs ("Heisenbugs") are timing-related and small changes like that can affect the timing. But it's not a robust solution.




回答3:


The problem could be solved by adding a timer:

  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();
    }

Thanks to JohnWein from MSDN



来源:https://stackoverflow.com/questions/12956288/avoiding-objectdisposedexception-while-calling-invoke

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!