Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Application.Exit event works even if handler is async void in WPF application lifecycle?

I have a problem how to await async methods in WPF life-cycle methods (with Caliburn-Micro framework) (eg. OnActivate, OnInitialized, OnExit - which is bound directly to Application.Exit event)

This article exactly describes my problem: http://mark.mymonster.nl/2013/07/10/donrsquot-make-your-application-lifetime-events-async-void (now I am thinking of using the solution from this article, but seems like a bit overkill for the first look)

I need to await some async methods in my OnExit hanlder so I have it as async. And it works. Kind of. I do not understand why??, but on calling Application.Exit event it somehow waits until the method is completed, even if the handler is async void. Can you explain please how this is possible? And is this safe? Or is it just coicidence? Async void should be used only for Top-Level events, is this that case?

I looked in the code of System. And the binding looks like this:

public event EventHandler Exit
{
  add
  {
    XcpImports.CheckThread();
    this.AddEventListener(DependencyProperty.RegisterCoreProperty(20053U, (Type) null), (Delegate) value);
  }
  remove
  {
    XcpImports.CheckThread();
    this.RemoveEventListener(DependencyProperty.RegisterCoreProperty(20053U, (Type) null), (Delegate) value);
  }
}

which is really cryptic and I cannot see what really happens in .net framework by calling this event.

What is as well strange, that calling await Task.Delay(1) in the handler causes DeadLock when I do not use ConfigureAwait(false). So I would say there is somewhere .Wait() used deep in .net code.

Note: when I make OnActivate, OnInitialized handlers async, as expected, page is not waiting till handler completes.

Thx for your answeres!

like image 326
Lukas K Avatar asked Sep 05 '13 13:09

Lukas K


People also ask

Why you shouldn t use async void?

Async void methods can wreak havoc if the caller isn't expecting them to be async. When the return type is Task, the caller knows it's dealing with a future operation; when the return type is void, the caller might assume the method is complete by the time it returns.

Can async return void?

In short, if your async method is an event handler or a callback, it's ok to return void .

Why async all the way?

So async all the way up enables you to actually make an asynchronous call and release any threads. If it isn't async all the way then some thread is being blocked. So unless you await all the way up, there's no benefit.

Should all methods be async C#?

If a method has no async operations inside it there's no benefit in making it async . You should only have async methods where you have an async operation (I/O, DB, etc.). If your application has a lot of these I/O methods and they spread throughout your code base, that's not a bad thing.


1 Answers

It is theoretically possible for a framework to detect the use of async void and wait until the async void method returns. I describe the details in my article on SynchronizationContext. AFAIK, ASP.NET is the only built-in framework that will wait on async void handlers.

WPF does not have any special treatment for async void methods. So the fact that your exit handler is completing is just coincidence. I suspect that the operations you await are either already complete or extremely fast, which allows your handler to complete synchronously.

That said, I do not recommend the solution in the article you referenced. Instead, handle the window's Closing event, kick off whatever asynchronous saving you need to do, and cancel the close command (and also consider hiding the window immediately). When the asynchronous operation is complete, then close the window again (and allow it to close this time). I use this pattern for doing asynchronous window-level "close" animations.

I'm unable to repro the deadlock you describe. I created a new .NET 4.5 WPF application and added an exit handler as such:

private async void Application_Exit(object sender, ExitEventArgs e)
{
    await Task.Delay(1);
}

but did not observe a deadlock. In fact, even with using Task.Yield, nothing after the await is ever executed, which is what I would expect.

like image 120
Stephen Cleary Avatar answered Nov 15 '22 05:11

Stephen Cleary