Damian Edwards from the ASP.NET team gave this answer:
Async void event handlers in web forms are only supported on certain
events, as you've found, but are really only intended for simplistic
tasks. We recommend using PageAsyncTask for any async work of any real
complexity.
Levi Broderick from the ASP.NET team gave this answer:
Async events in web applications are inherently strange beasts. Async
void is meant for a fire and forget programming model. This works in
Windows UI applications since the application sticks around until the
OS kills it, so whenever the async callback runs there is guaranteed
to be a UI thread that it can interact with. In web applications,
this model falls apart since requests are by definition transient. If
the async callback happens to run after the request has finished,
there is no guarantee that the data structures the callback needs to
interact with are still in a good state. Thus why fire and forget
(and async void) is inherently a bad idea in web applications.
That
said, we do crazy gymnastics to try to make very simple things like
Page_Load work, but the code to support this is extremely complicated
and not well-tested for anything beyond basic scenarios. So if you
need reliability I’d stick with RegisterAsyncTask.
So I think the answer to my question is: "That's the wrong question."
The right question would be "How should I be async in my ASP.NET Web Forms application?" And the answer is to insert this snippet inside your aspx code-behind file:
this.RegisterAsyncTask(new PageAsyncTask(async cancellationToken => {
var result = await SomeOperationAsync(cancellationToken);
// do something with result.
}));
This same trick works inside ASP.NET custom controls, just use this.Page.RegisterAsyncTask
instead.