Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding `ObjectDisposedException` while calling `Invoke`

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();
}
like image 527
Saeid Yazdani Avatar asked Oct 18 '12 13:10

Saeid Yazdani


2 Answers

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.

like image 107
Paul Ruane Avatar answered Oct 15 '22 19:10

Paul Ruane


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.

like image 32
Joe Avatar answered Oct 15 '22 19:10

Joe