2

I have a form that shows a data grid. I also have a method running on a different thread that updates only the displayed cells of the grid. To do this, this method calls a function on the form that returns the displayed cells.

The problem I have is that sometimes while the form has been closed and disposed the method on the other thread is still calling this function which results in an objectdisposed exception. Is there a way (other then making sure the methode on the other thread is finished) to prevent this?

So I need a thread safe method to kill the background task when the form is closed.

private delegate List<foo> GetShownCellsDelegate();
public List<foo> GetShownCells()
{
  if (this.InvokeRequired)
  {
    GetShownCellsDelegate getShownCellsDelegate = new GetShownCellsDelegate(GetShownCells);
    return (List<foo>)this.Invoke(getShownCellsDelegate);
  }
  else
  {
    //do stuff
  }
}

I tries using the IsDisposed property of the form:

if (!IsDisposed)
{
  return (List<foo>)this.Invoke(getShownCellsDelegate);
}

But apparently the form can be dispossed after the if statement because I still get the isdisposed exception.

This is how I use the function on the other thread:

private CancellationTokenSource cts = new CancellationTokenSource();
public void CancelUpdate()
{
  cts.Cancel();
}

public void ReadDataFromDevice()
{
  ThreadPool.QueueUserWorkItem(new WaitCallback(ReadAllDataThreadPoolMethod));
}

private void ReadAllDataThreadPoolMethod(Object stateInfo)
{
  if (!cts.IsCancellationRequested)
  {
    //do stuff
  }
}

The CancelUpdate method is called from the IsClosing event on the form. But I still get the isdisposed exception sometimes.

Marc.O
  • 125
  • 1
  • 11

2 Answers2

5

To cancel the long running operation you can use a CancellationToken, which is specifically designed for cooperative cancellation.

Have the main form create a CancellationTokenSource when starting the background thread, pass the CacellationToken generated by the CTS to the backround thread, cancel the CTS when your form closes, and then have the background thread check the token to see if it is cancelled before trying to invoke back to the main thread.

public void Foo()
{
    var cts = new CancellationTokenSource();
    var task = Task.Run(() => DoWork(cts.Token));
    FormClosing += (s, args) =>
    {
        cts.Cancel();
        if (!task.IsCompleted)
        {
            args.Cancel = true;
            task.ContinueWith(t => Close());
        }
    };
}
private void DoWork(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        //Do some  work
    }
}

To be absolutely sure that the background thread doesn't pass the cancellation check, then yield to the UI thread to have it cancel the token and dispose of the form, before the work is done, you'll also need to ensure that the background thread has time to run to completion after being cancelled, before the form closes. This can be done through a simple Thread.Join call in the closing handler.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • I edited my question to show how I used it. I still get the exception sometimes. – Marc.O May 22 '14 at 15:02
  • @Marc.O Quite right. It should be a hard race condition to hit, I'd expect, but it is there. See edit. – Servy May 22 '14 at 15:06
  • But then you are blocking thread until the UI thread is done? I do not think that will happen until you close the entire program? – Marc.O May 22 '14 at 15:21
  • @Marc.O Of course. So the only way to prevent that deadlock from happening is for the closing event to be cancelled until the background thread has time to run to completion. See edit again. – Servy May 22 '14 at 15:34
  • It works nicely this way. No more exceptions but now we are waiting till the background task is done before closing the form and my question was if there is an other way. – Marc.O May 23 '14 at 09:17
  • @Marc.O Not really, and the important point here is that you're stopping the background worker in its tracks, you're not waiting for it to finish normally, which means that there's a pretty good chance that you can end up closing the program without the human user perceiving the delay. Another option you have is to hide the form while it's shutting down and finish your cleanup without any visible UI. – Servy May 23 '14 at 13:37
0
this.FormClosed += new FormClosedEventHandler(form1_FormClosed);
void form1_FormClosed(object sender, FormClosedEventArgs e)
{
    //close thread
}

This will be executed whenever your form is being closed.

fishmong3r
  • 1,414
  • 4
  • 24
  • 51