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

1

5

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!

Webber answered 5/9, 2013 at 13:15 Comment(0)
R
12

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.

Roundworm answered 5/9, 2013 at 14:2 Comment(4)
I created as well fresh WPF app and couldn't reproduce the deadlock. I found out after some investigation that it was caused by our logging library which kept the application 'alive' and that was why OnExit was finished. Actualy the deadlock really happens, but without this logging component, you do not see it, as it is killed just after closing the app. Your pattern for closing the app sounds excelent, I will do it! But anyway, why do you not recommend the solution from the article? I still need a way how to await something eg in OnActivate method. Such helper would make my life easy :)Webber
The code in that article (and the SO answer it references) is not quite correct; IIRC it doesn't handle exceptions correctly. There are a ton of nuances in getting it just right, and even if you have a perfect implementation it still cannot work in every situation. Stephen Toub has the best summary of these kinds of hacks; note that every hack described has a serious drawback. There is no "one solution" that works everywhere.Roundworm
The best solution is to find alternatives, e.g., await in OnActivate is likely asynchronously initializing a some state used for display (i.e., a ViewModel); the proper solution in that case is to use something like the async initialization pattern that I describe on my blog. Note that this proper solution is more work: you have to purposely design a UI state for "loading" and then transition when the async init is complete.Roundworm
Thx for the great links! I try to use some of those patterns. But not sure how easy it will be, like you said, the UI has to be adapted to correctly wait for the initializations... I was afraid there is no simple solution for this.. As said in the first article - there are just situations where you need to wrap - I eg. have exactly the problem by implementing one interface for File access in WinRT and WPF. Probably it is better make async interface methods and wrap sync in async, but one way or another, you have to wrap :(Webber

© 2022 - 2024 — McMap. All rights reserved.