I have this event handler:
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
{
var canShutdown = lifetime.CanShutdown();
e.Cancel = !canShutdown;
}
Now, due to design decisions the CanShutdown
method has changed from bool
to Task<bool>
Task<bool> CanShutDown()
{
//...
}
So, I need to modify the event handler like this:
private async void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
{
var canShutdown = await lifetime.CanShutdown();
e.Cancel = !canShutdown;
}
I've read many times that async void methods have many problems, like unhandled exceptions being thrown directly inside the SynchronizationContext
. But one of the valid usages for them are event handlers. This is an event handler. Isn't it?
BUT, I wonder if that code is free of undesired consequences after the "migration".
My concern is that the handler modifies the value of e.Cancel.
A colleague has told me that this will happen:
After await, the caller to that method isn't awaiting. It assumes synchronous execution, so it immediately reads
e.Cancel
, and that hasn't been set yet. That is a problem inside event handler: You realize as soon as the await keyword is hit, the code that calledShutdownRequested.Invoke()
immediately returns. The value it will read might not be up-to-date.
I'm afraid my colleague has his point. So, it seems this approach is broken. But I still don't see how to fix that.
How to deal with the EventArgs
being shared by sync and async code?