Quantcast
Channel: Scott Hanselman's Blog
Viewing all articles
Browse latest Browse all 1148

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

$
0
0

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!



© 2013 Scott Hanselman. All rights reserved.
     

Viewing all articles
Browse latest Browse all 1148

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>