Search code examples
c#winformsasync-awaitdispose

Winform is disposed while still awaiting a task during the FormClosing event


I'm wanting to do some cleanup during the FormClosing event of a WinForm in a WinForms application. The cleanup code is async.

The FormClosing hander is:

private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    await DoCleanupAsync();

    // we never get here as the Form is disposed almost as soon as the async cleanup-method executes;
}

, with:

private async Task DoCleanupAsync()
{
    Console.WriteLine("stopping...");  // This is output
    await Task.Delay(2000);
    Console.WriteLine("stopped");      // This is not
}

What's happening is the Form is being disposed of before the await on the cleanup code is complete.

Incidentally, this is causing a race condition where I'm trying to update the form (for simplicity let's say the form contains log output) during the cleanup and I get an ObjectDisposedException because the form has been disposed.

If I look at the callstack when Dispose() is called, I can see it's being triggered from

System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) Unknown

, so we can see that the dispose is being triggered directly from the FormClosing event, and nothing that I'm doing myself.

I can work around this by doing something hacky like:

private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    e.Cancel = true;
    await DoCleanupAsync();
    this.FormClosing -= Form1_FormClosing;
    Close();
}

, but I'd like to understand what's actually going on!


Solution

  • what's actually going on

    When you await an async method, the control flow returns to the caller of your Form1_FormClosing method. And when you didn't set the e.Cancel flag at this time, the form is closed.

    The execution of your handler is resumed when the Task returned by DoCleanupAsync completed. But then the form is already closed.