I've been testing async in WebForms and for once, my question isn't about how to do something, it's about how something that already works, DOES work. This is my test code:
protected override void OnPreRender(EventArgs e)
{
Response.Write("OnPreRender<Br>");
}
protected override void OnPreRenderComplete(EventArgs e)
{
Response.Write("OnPreRenderComplete<Br>");
}
protected async override void OnLoadComplete(EventArgs e)
{
Response.Write("OnLoadComplete<br>");
var t1 = Task.Factory.StartNew(() => {
System.Threading.Thread.Sleep(2000);
return 1;
});
//This actually does run:
Response.Write((await t1).ToString());
}
So my task pauses for a bit and then writes out the result. My question is - I wouldn't expect this to work because control has been yielded from the OnLoadComplete method - I would expect the page to actually finish rendering and be returned to the client before my task ever returned.
The actual output is:
OnLoadComplete
OnPreRender
1OnPreRenderComplete
So it's clear that the OnLoadComplete method yielded control so that OnPreRender could run and then control returned to the OnLoadComplete. My expected result was that the "1" would never print because the subsequent events would fire and the page's thread would either be killed or the post-task write would happen after the response had been sent. I guess, given the above, it's not surprising that even if I delay for 10 seconds, the result is exactly the same.
I assume there's some wiring in the WebForm engine that ensures any awaitables are completed before the next phase of the page's life cycle proceeded. Does anyone know for sure how this happens? I'm afraid to use async/await in methods that need to complete before other events for fear that the continuation will be too late, but if it's handled internally, then I won't worry.
For ASP.NET, you should only use async
methods on .NET 4.5. I'll explain why at the end.
I have an article on SynchronizationContext
that helps fill in the blanks on how this works on ASP.NET. First, note that ASP.NET supported asynchronous operations a long time ago (.NET 2.0 IIRC). You could register asynchronous operations a few different ways, but for this description we'll focus on SynchronizationContext.OperationStarted
.
ASP.NET creates a SynchronizationContext
for each request, and it knows the request is not complete until all registered operations have completed (by calling SynchronizationContext.OperationCompleted
). Event-based asynchronous pattern components (such as BackgroundWorker
) will notify the SynchronizationContext
automatically when they start and complete.
Similarly, async void
methods (the new task-based asynchronous pattern) will notify the SynchronizationContext
automatically when they start and complete. So when you override OnLoadComplete
as an async void
method, the compiler inserts code for you that will call OperationStarted
at the beginning and OperationCompleted
when it completes.
So far so good - ASP.NET now knows to keep the request alive until all asynchronous operations for that request have completed. This is true even if there are no threads processing the request.
Now the caveat: ASP.NET before .NET 4.5 would handle this at a request level. In ASP.NET 4.5, the lifecycle pipeline was made smarter so that it would delay the page lifecycle until asynchronous operations completed. With the old ASP.NET, the "pre" handlers would start at that point in the pipeline but may not finish until later. The new ASP.NET will delay the rest of the page execution to ensure async
handlers complete before moving on in the lifecycle.
Also, ASP.NET 4.5 will detect if you've used an async
handler where there shouldn't be one, and will notify you of the error.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With