September 2010

Volume 25 Number 09

Async Tasks - Simplify Asynchronous Programming with Tasks

By Igor Ostrovsky | September 2010

Asynchronous programming is a collection of techniques for implementing expensive operations that run concurrently with the rest of the program. One domain where asynchronous programming often comes up is in the context of programs with a graphical UI: It’s generally unacceptable to freeze the UI while an expensive operation completes. Also, asynchronous operations are important for server applications that need to handle multiple client requests concurrently.

Representative examples of asynchronous operations that come up in practice include sending a request to a server and waiting for a response, reading data from the hard disk and running an expensive computation such as a spell check.

Consider the example of an application with a UI. The app could be built with Windows Presentation Foundation (WPF) or Windows Forms. In such an application, most of your code executes on the UI thread because it executes the event handlers for events that originate from the UI controls. When the user clicks a button, the UI thread will pick up the message, and execute your Click event handler.

Now, imagine that in the Click event handler, your application sends a request to a server and waits for a response:

// !!! Bad code !!!
void Button_Click(object sender, RoutedEventArgs e) {
  WebClient client = new WebClient();
  client.DownloadFile("http://www.microsoft.com", "index.html");
}

There’s a major problem in this code: downloading a Web site can take several seconds or longer. In turn, the call to Button_Click can take several seconds to return. That means the UI thread will be blocked for several seconds and the UI will be frozen. A frozen interface makes for a poor user experience and is almost always unacceptable.

To keep the application UI responsive until the server responds, it’s important that the download isn’t a synchronous operation on the UI thread.

Let’s try to fix the problem of the frozen UI. One possible (but suboptimal) solution is to communicate with the server on a different thread so the UI thread remains unblocked. Here’s an example that uses a thread-pool thread to talk to the server:

// Suboptimal code
void Button_Click(object sender, RoutedEventArgs e) {
  ThreadPool.QueueUserWorkItem(_ => {
    WebClient client = new WebClient();
    client.DownloadFile(
      "http://www.microsoft.com", "index.html");
  });
}

This code sample fixes the problem of the first version: now the Button_Click event does not block the UI thread, but the thread-based solution has three significant problems. Let’s take a closer look at these problems.

Problem 1: Wasted Thread-Pool Threads

The fix I just demonstrated uses a thread from the thread pool to send a request to the server and waits until the server responds.

The thread-pool thread is going to sit around blocked until the server responds. The thread cannot be returned to the pool until the call to WebClient.DownloadFile completes. Blocking a thread-pool thread is much better than blocking the UI thread because the UI will not freeze, but it does waste one thread from the thread pool.

If your application occasionally blocks a thread-pool thread for a while, the performance penalty may be negligible. But if your application does it a lot, its responsiveness can degrade due to pressure on the thread pool. The thread pool will attempt to cope by creating more threads, but that comes at a noticeable performance cost.

All other patterns of asynchronous programming presented in this article fix the problem of wasted thread-pool threads.

Problem 2: Returning the Result

There’s another difficulty with using threads for asynchronous programming: returning a value from the operation that executed on the helper thread gets a little messy.

In the initial example, the DownloadFile method writes the downloaded Web page into a local file, and so it has a void return value. Consider a different version of the problem—instead of writing the downloaded Web page into a file, you want to assign the received HTML into the Text property of a TextBox (named HtmlTextBox).

A naïve—and wrong—way to implement this would be as follows:

// !!! Broken code !!!
void Button_Click(object sender, RoutedEventArgs e) {
  ThreadPool.QueueUserWorkItem(_ => {
    WebClient client = new WebClient();
    string html = client.DownloadString(
      "http://www.microsoft.com", "index.html");
    HtmlTextBox.Text = html;
  }); 
}

The problem is that a UI control—HtmlTextBox—is getting modified from a thread-pool thread. That’s an error because only the UI thread is allowed to modify the UI. This restriction is present in both WPF and Windows Forms, for very good reasons.

To fix this issue, you can capture the synchronization context on the UI thread and then post a message to it on the thread-pool thread:

void Button_Click(object sender, RoutedEventArgs e) {
  SynchronizationContext ctx = SynchronizationContext.Current;
  ThreadPool.QueueUserWorkItem(_ => {
    WebClient client = new WebClient();
    string html = client.DownloadString(
      "http://www.microsoft.com");
    ctx.Post(state => {
      HtmlTextBox.Text = (string)state;
    }, html);
  });
}

It’s important to recognize that the problem of returning a value from a helper thread is not limited to applications with UIs. In general, returning a value from one thread to another is a tricky issue that requires usage of synchronization primitives.

Problem 3: Composing Asynchronous Operations

Explicitly working with threads also makes it difficult to compose asynchronous operations. For example, to download multiple Web pages in parallel, the synchronization code gets even more difficult to write and more error-prone.

Such an implementation would maintain a counter of asynchronous operations that are still executing. The counter would have to be modified in a thread-safe manner, say by using Interlocked.Decrement. Once the counter reaches zero, the code that processes the downloads would execute. All of this results in a non-trivial amount of code that’s easy to get wrong.

Needless to say, a more complicated composition pattern would become even more difficult to implement correctly using the thread-based pattern.

Event-Based Pattern

One common pattern for asynchronous programming with the Microsoft .NET Framework is the event-based model. The event model exposes a method to start the asynchronous operation and raises an event when the operation completes.

The event pattern is a convention for exposing asynchronous operations, but it’s not an explicit contract, such as via an interface. The class implementer can decide how faithfully to follow the pattern. Figure 1 shows an example of methods exposed by a correct implementation of the event-based asynchronous programming pattern.

Figure 1 Methods for an Event-Based Pattern

public class AsyncExample {
  // Synchronous methods.
  public int Method1(string param);
  public void Method2(double param);
  // Asynchronous methods.
  public void Method1Async(string param);
  public void Method1Async(string param, object userState);
  public event Method1CompletedEventHandler Method1Completed;
  public void Method2Async(double param);
  public void Method2Async(double param, object userState);
  public event Method2CompletedEventHandler Method2Completed;
  public void CancelAsync(object userState);
  public bool IsBusy { get; }
  // Class implementation not shown.
  ...
}

WebClient is one class in the .NET Framework that implements asynchronous operations via the event-based pattern. To provide an asynchronous variant of the DownloadString method, WebClient exposes the DownloadStringAsync and CancelAsync methods, and the DownloadStringCompleted event. This is how our sample would be implemented in an asynchronous way:

void Button_Click(object sender, RoutedEventArgs e) {
  WebClient client = new WebClient();
  client.DownloadStringCompleted += eventArgs => {
      HtmlTextBox.Text = eventArgs.Result;
  };
  client.DownloadStringAsync("http://www.microsoft.com");
}

This implementation resolves Problem 1 of the inefficient thread-based solution: unnecessary blocking of threads. The call to DownloadStringAsync returns immediately and does not block either the UI thread or a thread-pool thread. The download executes in the background and once it’s finished, the DownloadStringCompleted event will be executed on the appropriate thread.

Note that the DownloadStringCompleted event handler executes on the appropriate thread, without the need for the SynchronizationContext code I needed in the thread-based solution. Behind the scenes, WebClient automatically captures the SynchronizationContext and then posts the callback to the context. Classes that implement the event-based pattern will generally ensure that the Completed handler executes on the appropriate thread.

The event-based asynchronous programming pattern is efficient from the perspective of not blocking more threads than is necessary, and it’s one of the two patterns broadly used across the .NET Framework. However, the event-based pattern has several limitations:

  • The pattern is informal and by convention only—classes can deviate from it.
  • Multiple asynchronous operations can be quite difficult to compose, such as handling asynchronous operations launched in parallel, or handling a sequence of asynchronous operations.
  • You cannot poll and check whether the asynchronous operation is done.
  • Great care must be taken when utilizing these types. For example, if one instance is used to handle multiple asynchronous operations, a registered event handler must be coded to handle only the one asynchronous operation it’s targeting, even if it’s invoked multiple times.
  • Event handlers will always be invoked on the SynchronizationContext captured when the asynchronous operation was launched, even if executing on the UI thread is unnecessary, leading to additional performance costs.
  • It can be difficult to implement well and requires defining multiple types (for example, event handlers or event arguments).

Figure 2 lists several examples of .NET Framework 4 classes that implement the event-based asynchronous pattern.

Figure 2 Examples of the Event-Based Asynchronous Pattern in .NET Classes

Class Operation
System.Activities.WorkflowInvoker InvokeAsync
System.ComponentModel.BackgroundWorker RunWorkerAsync
System.Net.Mail.SmtpClient SendAsync
System.Net.NetworkInformation.Ping SendAsync
System.Net.WebClient DownloadStringAsync

IAsyncResult Pattern

Another convention for implementing asynchronous operations in .NET is the IAsyncResult pattern. Compared to the event-based model, IAsyncResult is a more advanced solution to asynchronous programming.

In the IAsyncResult pattern, an asynchronous operation is exposed using Begin and End methods. You call the Begin method to initiate the asynchronous operation, and pass in a delegate that will be called when the operation completes. From the callback, you call the End method, which returns the result of the asynchronous operation. Alternatively, instead of providing a callback, you can poll whether the operation has completed, or synchronously wait on it.

As an example, consider the Dns.GetHostAddresses method that accepts a hostname and returns an array of IP addresses that the hostname resolves to. The signature of the synchronous version of the method looks like this:

public static IPAddress[] GetHostAddresses(
  string hostNameOrAddress)
The asynchronous version of the method is exposed as follows:
public static IAsyncResult BeginGetHostAddresses(
  string hostNameOrAddress,
  AsyncCallback requestCallback,
  Object state)
public static IPAddress[] EndGetHostAddresses(
  IAsyncResult asyncResult)

Here’s an example that uses the BeginGetHostAddresses and EndGetHostAddresses methods to asynchronously query DNS for the address www.microsoft.com:

static void Main() {
  Dns.BeginGetHostAddresses(
    "www.microsoft.com",
    result => {
      IPAddress[] addresses = Dns.EndGetHostAddresses(result);
      Console.WriteLine(addresses[0]);
    }, 
    null);
  Console.ReadKey();
}

Figure 3 lists several .NET classes that implement an asynchronous operation using the event-based pattern. By comparing Figures 2 and 3, you’ll notice that some classes implement the event-based pattern, some implement the IAsyncResult pattern, and some implement both.

Figure 3 Examples of IAsyncResult in .NET Classes

Class Operation
System.Action BeginInvoke
System.IO.Stream BeginRead
System.Net.Dns BeginGetHostAddresses
System.Net.HttpWebRequest BeginGetResponse
System.Net.Sockets.Socket BeginSend
System.Text.RegularExpressions.MatchEvaluator BeginInvoke
System.Data.SqlClient.SqlCommand BeginExecuteReader
System.Web.DefaultHttpHandler BeginProcessRequest

From a historic perspective, the IAsyncResult pattern was introduced in the .NET Framework 1.0 as a high-performance approach to implementing asynchronous APIs. However, it requires additional work to interact with the UI thread, it’s difficult to implement correctly and it can be difficult to consume. The event-based pattern was introduced in the .NET Framework 2.0 to ease the UI-aspects left unaddressed by IAsyncResult, and is focused mostly on scenarios where a UI application launches a single asynchronous application and then works with it.

Task Pattern

A new type, System.Threading.Tasks.Task, was introduced in the .NET Framework 4 as a way to represent asynchronous operations. A Task can represent an ordinary computation that executes on a CPU:

static void Main() {
  Task<double> task = Task.Factory.StartNew(() => { 
    double result = 0; 
    for (int i = 0; i < 10000000; i++) 
      result += Math.Sqrt(i);
    return result;
  });
  Console.WriteLine("The task is running asynchronously...");
  task.Wait();
  Console.WriteLine("The task computed: {0}", task.Result);
}

Tasks created using the StartNew method correspond to Tasks that execute code on the thread pool by default. However, Tasks are more general and can represent arbitrary asynchronous operations—even those that correspond to, say, communication with a server or reading data from the disk.

TaskCompletionSource is the general mechanism for creating Tasks that represent asynchronous operations. TaskCompletionSource is associated with exactly one task. Once the SetResult method is called on the TaskCompletionSource, the associated Task completes, returning the result value of the Task (see Figure 4).

Figure 4 Using TaskCompletionSource

static void Main() {
  // Construct a TaskCompletionSource and get its 
  // associated Task
  TaskCompletionSource<int> tcs = 
    new TaskCompletionSource<int>();
  Task<int> task = tcs.Task;
  // Asynchronously, call SetResult on TaskCompletionSource
  ThreadPool.QueueUserWorkItem( _ => {
    Thread.Sleep(1000); // Do something
    tcs.SetResult(123);
  });
  Console.WriteLine(
    "The operation is executing asynchronously...");
  task.Wait();
  // And get the result that was placed into the task by 
  // the TaskCompletionSource
  Console.WriteLine("The task computed: {0}", task.Result);
}

Here I use a thread-pool thread to call SetResult on the TaskCompletionSource. However, an important point to notice is that the SetResult method could be called by any code that has access to the TaskCompletionSource—an event handler for a Button.Click event, a Task that completed some computation, an event raised because a server responded to a request, and so forth.

So, the TaskCompletionSource is a very general mechanism for implementing asynchronous operations.

Converting an IAsyncResult Pattern

To use Tasks for asynchronous programming, it’s important to be able to interoperate with asynchronous operations exposed using the older models. While TaskCompletionSource can wrap any asynchronous operation and expose it as a Task, the Task API provides a convenient mechanism to convert an IAsyncResult pattern to a Task: the FromAsync method.

This example uses the FromAsync method to convert the IAsync­Result-based asynchronous operation Dns.BeginGetHost­ Addresses into a Task:

static void Main() {
  Task<IPAddress[]> task = 
    Task<IPAddress[]>.Factory.FromAsync(
      Dns.BeginGetHostAddresses, 
      Dns.EndGetHostAddresses,
      "http://www.microsoft.com", null);
  ...
}

FromAsync makes it easy to convert IAsyncResult asynchronous operations to tasks. Under the covers, FromAsync is implemented in a manner similar to the example for TaskCompletionSource utilizing the ThreadPool. Here’s a simple approximation of how it’s implemented, in this case targeting GetHostAddresses directly:

static Task<IPAddress[]> GetHostAddressesAsTask(
  string hostNameOrAddress) {
  var tcs = new TaskCompletionSource<IPAddress[]>();
  Dns.BeginGetHostAddresses(hostNameOrAddress, iar => {
    try { 
      tcs.SetResult(Dns.EndGetHostAddresses(iar)); }
    catch(Exception exc) { tcs.SetException(exc); }
  }, null);
  return tcs.Task;
}

Converting an Event-Based Pattern

Event-based asynchronous operations can also be converted to Tasks using the TaskCompletionSource class. The Task class does not provide a built-in mechanism for this conversion—a general mechanism is impractical because the event-based asynchronous pattern is a convention only.

Here’s how to convert an event-based asynchronous operation into a task. The code sample shows a method that takes a Uri and returns a Task that represents the asynchronous operation WebClient.DownloadStringAsync:

static Task<string> DownloadStringAsTask(Uri address) {
  TaskCompletionSource<string> tcs = 
    new TaskCompletionSource<string>();
  WebClient client = new WebClient();
  client.DownloadStringCompleted += (sender, args) => {
    if (args.Error != null) tcs.SetException(args.Error);
    else if (args.Cancelled) tcs.SetCanceled();
    else tcs.SetResult(args.Result);
  };
  client.DownloadStringAsync(address);
  return tcs.Task;
}

Using this pattern and the pattern in the previous section, you can convert any existing asynchronous pattern—event-based or IAsyncResult-based—into a Task.

Manipulating and Composing Tasks

So, why would you use Tasks to represent asynchronous operations? The main reason is that Tasks expose methods to conveniently manipulate and compose asynchronous operations. Unlike both the IAsyncResult and event-based approaches, a Task provides a single object that maintains all relevant information about the asynchronous operation, how to join with it, how to retrieve its result and so on.

One useful thing you can do with a Task is to wait until it completes. You can wait on one Task, wait until all Tasks in a set complete, or wait until any Task in a set completes.

static void Main() {
  Task<int> task1 = new Task<int>(() => ComputeSomething(0));
  Task<int> task2 = new Task<int>(() => ComputeSomething(1));
  Task<int> task3 = new Task<int>(() => ComputeSomething(2));
  task1.Wait();
  Console.WriteLine("Task 1 is definitely done.");
  Task.WaitAny(task2, task3);
  Console.WriteLine("Task 2 or task 3 is also done.");
  Task.WaitAll(task1, task2, task3);
  Console.WriteLine("All tasks are done.");
}

Another useful capability of Tasks is the ability to schedule continuations: Tasks that execute as soon as another Task completes. Similar to waiting, you can schedule continuations that run when a particular Task completes, when all Tasks in a set complete or when any Task in a set completes.

This example creates a task that will query DNS for the address www.microsoft.com. Once that task completes, the continuation task is kicked-off and will print the result to the console:

static void Main() {
  Task<IPAddress[]> task = 
    Task<IPAddress[]>.Factory.FromAsync(
      Dns.BeginGetHostAddresses, 
      Dns.EndGetHostAddresses,
      "www.microsoft.com", null);
  task.ContinueWith(t => Console.WriteLine(t.Result));
  Console.ReadKey();
}

Let’s take a look at more interesting examples that show off the power of the task as a representation of an asynchronous operation. Figure 5 shows an example that runs two DNS lookups in parallel. When the asynchronous operations are represented as tasks, it’s easy to wait until multiple operations have completed.

Figure 5 Running Operations in Parallel

static void Main() {
  string[] urls = new[] { "www.microsoft.com", "www.msdn.com" };
  Task<IPAddress[]>[] tasks = new Task<IPAddress[]>[urls.Length];
  for(int i=0; i<urls.Length; i++) {
    tasks[i] = Task<IPAddress[]>.Factory.FromAsync(
      Dns.BeginGetHostAddresses,
      Dns.EndGetHostAddresses,
      urls[i], null);
  }
  Task.WaitAll(tasks);
  Console.WriteLine(
    "microsoft.com resolves to {0} IP addresses. msdn.com resolves to {1}",
    tasks[0].Result.Length,
    tasks[1].Result.Length);
}

Let’s take a look at another example of composing tasks that takes the following three steps:

  1. Asynchronously download multiple HTML pages in parallel
  2. Process the HTML pages
  3. Aggregate the information from the HTML pages

Figure 6shows how such computation would be implemented, by taking advantage of the DownloadStringAsTask method shown earlier in this article. One notable benefit of this implementation is that the two different CountParagraphs methods execute on different threads. Given the prevalence of multi-core machines today, a program that spreads its computationally expensive work across multiple threads will get a performance benefit.

Figure 6 Downloading Strings Asynchronously

static void Main() {
  Task<string> page1Task = DownloadStringAsTask(
    new Uri("http://www.microsoft.com"));
  Task<string> page2Task = DownloadStringAsTask(
    new Uri("http://www.msdn.com"));
  Task<int> count1Task = 
    page1Task.ContinueWith(t => CountParagraphs(t.Result));
  Task<int> count2Task = 
    page2Task.ContinueWith(t => CountParagraphs(t.Result));
  Task.Factory.ContinueWhenAll(
    new[] { count1Task, count2Task },
    tasks => {
      Console.WriteLine(
        "<P> tags on microsoft.com: {0}", 
        count1Task.Result);
      Console.WriteLine(
        "<P> tags on msdn.com: {0}", 
        count2Task.Result);
  });
        
  Console.ReadKey();
}

Running Tasks in a Synchronization Context

Sometimes it’s useful to be able to schedule a continuation that will run in a particular synchronization context. For example, in applications with a UI, it’s often useful to be able to schedule a continuation that will execute on the UI thread.

The easiest way to have a Task interact with a synchronization context is to create a TaskScheduler that captures the context of the current thread. To get a TaskScheduler for the UI thread, invoke the FromCurrentSynchronizationContext static method on the TaskScheduler type while running on the UI thread.

This example asynchronously downloads the www.microsoft.com Web page and then assigns the downloaded HTML into the Text property of a WPF text box:

void Button_Click(object sender, RoutedEventArgs e) {
  TaskScheduler uiTaskScheduler =
    TaskScheduler.FromCurrentSynchronizationContext()
  DownloadStringAsTask(new Uri("http://www.microsoft.com"))
    .ContinueWith(
       t => { textBox1.Text = t.Result; },
       uiTaskScheduler);
}

The body of the Button_Click method will set up the asynchronous computation that eventually updates the UI, but Button_Click does not wait until the computation completes. That way, the UI thread will not be blocked, and can continue updating the user interface and responding to user actions.

As I mentioned previously, prior to the .NET Framework 4, asynchronous operations were typically exposed using either the IAsyncResult pattern or the event-based pattern. With the .NET Framework 4, you can now employ the Task class as another useful representation of asynchronous operations. When represented as tasks, asynchronous operations are often easier to manipulate and compose. More examples on using tasks for asynchronous programming are included in the ParallelExtensionsExtras samples, available for download at code.msdn.microsoft.com/ParExtSamples.


Igor Ostrovsky is a software development engineer on the Parallel Computing Platform team at Microsoft. Ostrovsky documents his adventures in programming at igoro.com and contributes to the Parallel Programming with .NET blog at blogs.msdn.com/pfxteam.

Thanks to the following technical experts for reviewing this article: Concurrency Runtime team