Scott Hanselman

The Magic of using Asynchronous Methods in ASP.NET 4.5 plus an important gotcha

August 29, '13 Comments [35] Posted in ASP.NET
Sponsored By

First, I encourage you to listen to episode 327 of the Hanselminutes podcast. We called it "Everything .NET programmers know about Asynchronous Programming is wrong" and I learned a lot. I promise you will too.

Often we'll find ourselves doing three or four things on one page, loading stuff from a number of places. Perhaps you're loading something from disk, calling a web service, and calling a database.

You can do those things in order, synchronously, as is typical. Add up the duration of each Task:

public void Page_Load(object sender, EventArgs e)
{
var clientcontacts = Client.DownloadString("api/contacts");
var clienttemperature = Client.DownloadString("api/temperature");
var clientlocation = Client.DownloadString("api/location");


var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(clientcontacts);
var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(clientlocation);
var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(clienttemperature);

listcontacts.DataSource = contacts;
listcontacts.DataBind();
Temparature.Text = temperature;
Location.Text = location;
}

Each of this three calls takes about a second, so the total type is 3 seconds. They happen one after the other.

Intuitively you may want to make these async by marking the public void Page_Load with async and then awaiting three tasks.

However, Page_Load is a page lifecycle event, and it's a void event handler. Damian Edwards from the ASP.NET team says:

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, also from the ASP.NET team uses even stronger language. Basically, DO NOT use async on void event handlers like this, it's not worth it.

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.

Using async with voids is not stable or reliable. However, all you have to do is call Page.RegisterAyncTask - it's not any trouble and you'll be in a better more flexible place.

public void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(LoadSomeData));
}

public async Task LoadSomeData()
{

var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
var clientlocation = Client.DownloadStringTaskAsync("api/location");

await Task.WhenAll(clientcontacts, clienttemperature, clientlocation);

var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);

listcontacts.DataSource = contacts;
listcontacts.DataBind();
Temparature.Text = temperature;
Location.Text = location;
}

You can simplify this even more by removing the (in this case totally unnecessary) Task.WhenAll and just awaiting the result of the Tasks one by one. By the time Task.WhenAll  has happened here, the tasks are already back. The result is the same. This also has the benefit of reading like synchronous code while giving the benefits of async.

public async Task LoadSomeData()
{

var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
var clientlocation = Client.DownloadStringTaskAsync("api/location");

var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);

listcontacts.DataSource = contacts;
listcontacts.DataBind();
Temparature.Text = temperature;
Location.Text = location;
}

This now takes just a hair over a second, because the three async tasks are happening concurrently. Async stuff like this is most useful (and most obvious) when you have multiple tasks that don't depend on one another.

Do remember to mark the .aspx page as Async="true" like this:

<%@ Page Title="Async" Language="C#" CodeBehind="Async.aspx.cs" Inherits="Whatever" Async="true" %>

Related Links


Sponsor: A huge thank you to my friends at Red Gate for their support of the site this week. Check out Deployment Manager! Easy release management - Deploy your .NET apps, services and SQL Server databases in a single, repeatable process with Red Gate’s Deployment Manager. There’s a free Starter edition, so get started now!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Thursday, August 29, 2013 5:31:21 AM UTC
Ever since I read about async and ASP.NET (back in 2006? 2008?), I am having a very hard time understanding the reasons.

To me "async" in the context of ASP.NET means: Responsive in terms of users don't have to wait long until a page is loaded after a postback.

To the ASP.NET team it seems to mean more of a: The users still needs to wait as long as ever, you simply can do several things in parallel on the server.

So until now I see no benefits at all. When would I really want to do multiple parts in parallel on the server? I really don't get it.

When wanting user-centric asnyc behaviour, I would do some Ajax call to the server and would be done.

So instead on describing how "cool" server-side async is, I would love to see real world example usages that justify using server-side asnyc.

Or maybe I'm completely wrong...
Thursday, August 29, 2013 6:49:44 AM UTC
Uwe,

There are two potential benefits, but first let's clear up some confusion.

Using "await" in ASP.NET does not change the fact that HTTP is a fundamentally synchronous protocol. The client makes the request, and must wait for the results from the server. Whether those results are made up of async calls internally or synchronous ones is irrelevant from the point of view of the client: the call takes as long as it takes to complete. "async" is an implementation detail.

So, why use it? Those two potential benefits are:

1. Parallelization. As you see in the example above, you can parallelize work, and therefore return the data to the client sooner. The client spends less time waiting. However, this isn't necessarily free: if the tasks are I/O bound, then there is great benefit to the parallelization (as it is here); however, if the tasks are compute bound, then you're just spinning up several threads that are now bound to computation that would otherwise be able to service requests. That is, in the compute bound scenario, you're trading scalability (how many clients can you can service) for performance (how quickly you can return the response to a single client).

2. Freeing up otherwise pointlessly waiting threads. In the case of I/O bound operations (whether that's disk I/O or network I/O), the waiting that a thread might normally do is purposeless. Using an async version of the I/O method allows the worker thread -- which are generally in limited supply -- to go back into the worker pool to start servicing another request. When the I/O is ready, and a thread from the worker pool is available, then the original request can be completed. This can have a fairly substantial benefit in scalability of the server, because it means servicing lots more requests with far fewer resources.

Hope this helps shine some light on why "async" is a benefit for ASP.NET apps.
Thursday, August 29, 2013 7:31:20 AM UTC
Hey Scott - is there an MVC equivalent podcast/article for this?
Peter
Thursday, August 29, 2013 9:12:48 AM UTC
What about the SynchronizationContext? TaskScheduler ? aspnet:UseTaskFriendlySynchronizationContext appSetting are they necessary?
Thursday, August 29, 2013 12:20:17 PM UTC
Very Nice Scott
Kamran
Thursday, August 29, 2013 12:20:22 PM UTC
Nice blog post, Scott. Despite the prevalence of async/await the past few years, this is the best example I've read for using it in the page lifecycle. Thanks.
Sam
Thursday, August 29, 2013 1:20:28 PM UTC
Thanks Scott,
I really needed this. Problem is, the production server is running Windows 2003 server so I am locked at .net 3.5. I cannot use async :-(
My app had to load 18,000 records from a spreadsheet.
My solution was to write a winforms app just to load legacy data since winforms is more tolerant.
James
Thursday, August 29, 2013 1:46:10 PM UTC
The big problem I ran into with RegisterAsyncTask is that the task doesn't run until the PreRenderComplete stage. Most controls which register client script includes do so at the PreRender stage, and only if the control is visible.

If I use the results of the async operation to change the visibility of some controls - e.g. changing the active view in a MultiView control - the wrong scripts have been registered, and my page doesn't work as intended.

Is there any way to resolve this without resorting to an async void handler?
Richard
Thursday, August 29, 2013 2:05:30 PM UTC
@Nuno: async/await on ASP.NET requires .NET 4.5, and you have to avoid the "quirks mode" that 4.5 will run in by default. You turn off the "quirks" by either setting httpRuntime.targetFramework to 4.5 (which turns off all the quirks) or using an appSetting of aspnet:UseTaskFriendlySynchronizationContext of true (which turns off just the SynchronizationContext quirk).

Full details are on this blog post.
Thursday, August 29, 2013 2:21:08 PM UTC
I can't agree with the sternness of this warning. async void uses the same completion notification mechanisms as event-based asynchronous components (e.g., WebClient), which have been supported since .NET 2.0. It's true that 4.5 introduces a new SynchronizationContext, so there may be some untested corner conditions, but unless they broke EAP, async void would work just as well. Put another way: if async void is broken for some obscure scenario in ASP.NET, then EAP is broken in the same way.

So, the blanket statement of "not stable or reliable" is IMO going too far. Of course, async void methods should be avoided, but to be clear, the ASP.NET runtime *does* support them. Also, there are checks in the page lifecycle, so if you use async void at an inappropriate time, ASP.NET will immediately raise an exception.

For myself, I do recommend using RegisterAsyncTask if possible, but don't freak out if you have to fall back on async void. E.g., the major drawbacks of RegisterAsyncTask (as pointed out by @Richard) are that it doesn't start the background tasks running immediately and there's only a single "sync" point where we wait for them all to complete at the same time.

On a side note, your Task.WhenAll recommendation only works well because it's a contrived example (all tasks always take the same amount of time). In real-world code, I'd recommend Task.WhenAll.
Thursday, August 29, 2013 2:23:46 PM UTC
@Peter: There's a good article on the asp.net site. Note that MVC understands async Task actions directly, so async fits better into MVC than WebForms. In MVC there's no need for async void or RegisterAsyncTask.
Thursday, August 29, 2013 3:08:35 PM UTC
Would you recommend using async/await for heavy processor bound tasks? Say if your were converting an incoming xml blob into a PDF.

Async/await has always been billed as the "avoid UI thread blocking" scenario for IO calls. Does it make sense to run a processor bound task that could take several seconds to complete asynchronously? If so what kind of pitfalls should I expect?
Ben
Thursday, August 29, 2013 3:31:09 PM UTC
In the Task.WhenAll scenario, wouldn't you use .Result instead of awaiting the task again?

@Ben - take a look at Task.Run; you can spin off a thread for the background work, freeing up the web worker thread while that conversion is processed.
Thursday, August 29, 2013 3:56:20 PM UTC
@Daniel that's a good point, Task.Run would work perfectly in my scenario. However, I was looking for clarification on async best practice.

I've heard that async/await should only be used for IO operations (file system, network, web, ect.). Most of the async articles are referencing Windows desktop solutions, which place heavy emphasis on not blocking the UI thread.

I want to understand some of the differences that a web server environment will encounter in comparison to what I'm hearing from the Win App crowd.
Ben
Thursday, August 29, 2013 4:15:20 PM UTC
Wouldn't the first call to await in the non Task.WhenAll prevent the others from running at the same time?
Taken from MSDN "The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes".
I thought that's why we had Task.WhenAll so we could await multiple async tasks.
Ben Houghton
Thursday, August 29, 2013 5:00:16 PM UTC
Asynchronous methods should be suffixed with Async (or TaskAsync if a non Task returning suffixed Async already exists).
Thursday, August 29, 2013 8:25:38 PM UTC
Anyone know how you can get these async methods working in SharePoint given you can't change the Page directive?
Mark Wilson
Thursday, August 29, 2013 8:30:29 PM UTC
Scott, I really don't understand what PageAsyncTask does to the page lifecycle. Does Page_load stop until that task is done? Does the end of Page_load no longer indicate the end of page loading?

This seems like the page lifecycle was subverted to allow this async stuff to happen.
Tyrsius
Friday, August 30, 2013 7:54:41 AM UTC
@Stephen, could you explain some more why you would use Task.WhenAll here? I think that if the tasks would take 1, 3 and 2 seconds respectively, total time would still be about 3 seconds. I don't see how using Task.WhenAll would make a difference.
Friday, August 30, 2013 7:59:13 AM UTC
@Ben, to me that sounds like the current method execution suspends at that point but the tasks are still running.
Friday, August 30, 2013 9:54:09 AM UTC
@Ben, @Daniel: You should not use Task.Run on the server side. It makes sense on the client (to free up the UI thread), but it does not make sense on the server side (you're freeing up a worker thread... by using another worker thread). This is covered in an excellent video on Channel9.

@Daniel: You should not use Task.Result in async code; it's easy to cause deadlocks.

@Ben: All three tasks are running when you call the first await, so even though that method is suspended, the other tasks are already running.

@MarcelWijnands: It's the difference between "wait for the first task, then continue the method, wait for the second task, then continue the method, wait for the third task, then continue the method" and "wait for all tasks, then continue the method". The Task.WhenAll approach has less switching back and forth.
Friday, August 30, 2013 10:00:45 AM UTC
@Tyrsius: RegisterAsyncTask allows a page to register work that is executed just before PreRenderComplete (unless you call ExecuteRegisteredAsyncTasks early). So if a Page_Load just registered async work, then that's all it does and it's considered loaded once the registering is done.

This was added to the page lifecycle when asynchronous pages were first introduced in .NET 2.0. In .NET 4.5, the PageAsyncTask was made async-aware, but it still uses the same mechanism to execute the asynchronous code before PreRenderComplete.
Friday, August 30, 2013 12:10:20 PM UTC
@Stephen Cleary: From what I can see, calling ExecuteRegisteredAsyncTasks in an application configured for 4.5 has no effect:

public void ExecuteRegisteredAsyncTasks()
{
if (_legacyAsyncTaskManager != null && !_legacyAsyncTaskManager.TaskExecutionInProgress)
{
HttpAsyncResult result = _legacyAsyncTaskManager.ExecuteTasks(null, null);
if (result.Error != null)
{
throw new HttpException(null, result.Error);
}
}
}

The _legacyAsyncTaskManager field is assigned in the RegisterAsyncTask method if you're using the legacy synchronization context. As soon as you set targetFramework="4.5" on the httpRuntime element, you're using the new task-friendly synchronization context, and this field won't be assigned.
Richard
Friday, August 30, 2013 2:48:07 PM UTC
Is it recommended to use async/await only at the level of the actual I/O call, or is it preferred to develop in an "async all the way down" style and keep everything async up to the highest possible level of abstraction?

For example, say at some point in a blog application you're calling out to a database to get the currently logged in user's profile. Would it be better to have your MyBlog.GetCurrentUser do the call asynchronously but return a User object, or return a Task<User> and don't wait on the result until just before it is displayed?
Friday, August 30, 2013 3:28:33 PM UTC
Nice.
It's a shame Sharepoint2013 doesn't allow Async="true"
Alberto Sao Marcos
Friday, August 30, 2013 5:00:40 PM UTC
For me, as a non-native speaker of English, the term "synchronous" in programming has always been, and still is, confusing.

According to the dictionary "synchronous" is "happening, existing, or arising at precisely the same time", e.g. "Synchronized swimming".

Yet everybody says, like Mr. Hanselman here, "You can do those things in order, synchronously". I would think it is called "consecutive", exact opposite of "synchronous"

Anyway, thanks for an informative article.
Oleg
Saturday, August 31, 2013 3:10:24 AM UTC
I'm curious as to why you went stuck the await in the method parameters rather than awaiting the result at the call site itself? It seems more readable at the call site and the variable names seem more appropriate to the actual result rather than the task.


var clientcontacts = await Client.DownloadStringTaskAsync("api/contacts");
.
.
.
var contacts = JsonConvert.DeserializeObject<List<Contact>>(clientcontacts);
Saturday, August 31, 2013 7:44:24 AM UTC
Classic Asp run fast and quickly
Giorgio
Saturday, August 31, 2013 4:26:48 PM UTC
Why didn't use Newtonsoft.Json.JsonConvert.DeserializeObjectAsync method ? there is a good reason ? It's more difficult ?
Sunday, September 01, 2013 5:56:46 AM UTC
Well done Scott, simple and effective.
I've a suggestion, you can add 2 simple graphs, one to visualize the async 3 tasks, and the other to visualize the normal 3 tasks, this will give reader the benefit of parallelism like Brad Wilson wrote above.
Monday, September 02, 2013 10:32:27 PM UTC
Thanks Scott for sharing. This all make sense in terms of web and the post is a handy reminder to all web devs out there, including me.
Tuesday, September 03, 2013 7:40:09 AM UTC
@Tim Awaiting the result at the call sites would be the same as running the calls synchronously (but with more overhead).
Stan
Tuesday, September 03, 2013 11:50:29 AM UTC
Hi Scott,

I have been fiddling around with parallel.foreach, async, httpclient, postasync...the only thing I never understood was, Why didn't Microsoft made Interface for HttpClient? It would be pretty sick using IHttpClient for mocking and make life easier.

Secondly why web api uses datacontract serializer by default? can we not change it to you xml and json by default in web.config or somewhere?

we do have below lines in app_start

configuration.Formatters.Clear();
configuration.Formatters.Add(new XmlMediaTypeFormatter
{
Indent = false,
UseXmlSerializer = true
});
configuration.Formatters.Add(new JsonMediaTypeFormatter());

but when you need to post the data you still need to tell httpclient to use xmlserializer. Thats the only problems I have encountered right now (can be more).
Kamran
Friday, September 06, 2013 8:21:03 PM UTC
Very useful, I will make this example using razor. I hope I get success, thanks
Saturday, September 07, 2013 3:31:11 PM UTC
@Stan - doh! total brainfart.
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.