Most applications need to deal with more than one thing happening at a time (concurrency). In this chapter, we start with the essential prerequisites, namely the basics of threading and tasks, and then describe the principles of asynchrony and C#’s asynchronous functions in detail.
In Chapter 22, we’ll revisit multithreading in greater detail, and in Chapter 23, we’ll cover the related topic of parallel programming.
The most common concurrency scenarios are:
The general mechanism by which a program can simultaneously execute code is called multithreading. Multithreading is supported by both the CLR and operating system and is a fundamental concept in concurrency. Understanding the basics of threading, and in particular, the effects of threads on shared state, is therefore essential.
A thread is an execution path that can proceed independently of others.
Each thread runs within an operating system process, which provides an isolated environment in which a program runs. With a single-threaded program, just one thread runs in the process’s isolated environment, and so that thread has exclusive access to it. With a multithreaded program, multiple threads run in a single process, sharing the same execution environment (memory, in particular). This, in part, is why multithreading is useful: one thread can fetch data in the background, for instance, while another thread displays the data as it arrives. This data is referred to as shared state.
In Windows Store apps, you cannot create and start threads directly; instead you must do this via tasks (see “Tasks”). Tasks add a layer of indirection that complicates learning, so the best way to start is with Console applications (or LINQPad) and create threads directly until you’re comfortable with how they work.
A client program (Console, WPF, Windows Store, or Windows Forms) starts in a single thread that’s created automatically by the operating system (the “main” thread). Here it lives out its life as a single-threaded application, unless you do otherwise by creating more threads (directly or indirectly).1
You can create and start a new thread by instantiating a Thread
object and calling its Start
method. The simplest constructor for Thread
takes a ThreadStart
delegate: a parameterless method indicating where execution should begin. For example:
// NB: All samples in this chapter assume the following namespace imports: using System; using System.Threading; class ThreadTest { static void Main() { Thread t = new Thread (WriteY); // Kick off a new thread t.Start(); // running WriteY() // Simultaneously, do something on the main thread. for (int i = 0; i < 1000; i++) Console.Write ("x"); } static void WriteY() { for (int i = 0; i < 1000; i++) Console.Write ("y"); } } // Typical Output: xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
The main thread creates a new thread t
on which it runs a method that repeatedly prints the character y. Simultaneously, the main thread repeatedly prints the character x, as shown in Figure 14-1. On a single-core computer, the operating system must allocate “slices” of time to each thread (typically 20 ms in Windows) to simulate concurrency, resulting in repeated blocks of x and y. On a multicore or multiprocessor machine, the two threads can genuinely execute in parallel (subject to competition by other active processes on the computer), although you still get repeated blocks of x and y in this example because of subtleties in the mechanism by which Console
handles concurrent requests.
A thread is said to be preempted at the points where its execution is interspersed with the execution of code on another thread. The term often crops up in explaining why something has gone wrong!
Once started, a thread’s IsAlive
property returns true
, until the point where the thread ends. A thread ends when the delegate passed to the Thread
’s constructor finishes executing. Once ended, a thread cannot restart.
Each thread has a Name
property that you can set for the benefit of debugging. This is particularly useful in Visual Studio, since the thread’s name is displayed in the Threads Window and Debug Location toolbar. You can set a thread’s name just once; attempts to change it later will throw an exception.
The static Thread.CurrentThread
property gives you the currently executing thread:
Console.WriteLine (Thread.CurrentThread.Name);
You can wait for another thread to end by calling its Join
method:
static void Main() { Thread t = new Thread (Go); t.Start(); t.Join(); Console.WriteLine ("Thread t has ended!"); } static void Go() { for (int i = 0; i < 1000; i++) Console.Write ("y"); }
This prints “y” 1,000 times, followed by “Thread t has ended!” immediately afterward. You can include a timeout when calling Join
, either in milliseconds or as a TimeSpan
. It then returns true
if the thread ended or false
if it timed out.
Thread.Sleep
pauses the current thread for a specified period:
Thread.Sleep (TimeSpan.FromHours (1)); // Sleep for 1 hour Thread.Sleep (500); // Sleep for 500 milliseconds
Thread.Sleep(0)
relinquishes the thread’s current time slice immediately, voluntarily handing over the CPU to other threads. Thread.Yield()
does the same thing—except that it relinquishes only to threads running on the same processor.
Sleep(0)
or Yield
is occasionally useful in production code for advanced performance tweaks. It’s also an excellent diagnostic tool for helping to uncover thread safety issues: if inserting Thread.Yield()
anywhere in your code breaks the program, you almost certainly have a bug.
While waiting on a Sleep
or Join
, a thread is blocked.
A thread is deemed blocked when its execution is paused for some reason, such as when Sleep
ing or waiting for another to end via Join
. A blocked thread immediately yields its processor time slice, and from then on consumes no processor time until its blocking condition is satisfied. You can test for a thread being blocked via its ThreadState
property:
bool blocked = (someThread.ThreadState & ThreadState.WaitSleepJoin) != 0;
ThreadState
is a flags enum, combining three “layers” of data in a bitwise fashion. Most values, however, are redundant, unused, or deprecated. The following extension method strips a ThreadState
to one of four useful values: Unstarted
, Running
, WaitSleepJoin
, and Stopped
:
public static ThreadState Simplify (this ThreadState ts) { return ts & (ThreadState.Unstarted | ThreadState.WaitSleepJoin | ThreadState.Stopped); }
The ThreadState
property is useful for diagnostic purposes, but unsuitable for synchronization because a thread’s state may change in between testing ThreadState
and acting on that information.
When a thread blocks or unblocks, the operating system performs a context switch. This incurs a small overhead, typically one or two microseconds.
An operation that spends most of its time waiting for something to happen is called I/O-bound—an example is downloading a web page or calling Console.ReadLine
. (I/O-bound operations typically involve input or output, but this is not a hard requirement: Thread.Sleep
is also deemed I/O-bound.) In contrast, an operation that spends most of its time performing CPU-intensive work is called compute-bound.
An I/O-bound operation works in one of two ways: it either waits synchronously on the current thread until the operation is complete (such as Console.ReadLine
, Thread.Sleep
, or Thread.Join
), or operates asynchronously, firing a callback when the operation finishes some time later (more on this later).
I/O-bound operations that wait synchronously spend most of their time blocking a thread. They may also “spin” in a loop periodically:
while (DateTime.Now < nextStartTime) Thread.Sleep (100);
Leaving aside that there are better ways to do this (such as timers or signaling constructs), another option is that a thread may spin continuously:
while (DateTime.Now < nextStartTime);
In general, this is very wasteful on processor time: as far as the CLR and operating system are concerned, the thread is performing an important calculation and so gets allocated resources accordingly. In effect, we’ve turned what should be an I/O-bound operation into a compute-bound operation.
There are a couple of nuances with regard spinning versus blocking. First, spinning very briefly can be effective when you expect a condition to be satisfied soon (perhaps within a few microseconds) because it avoids the overhead and latency of a context switch. The .NET Framework provides special methods and classes to assist—see “SpinLock and SpinWait” in http://albahari.com/threading/.
Second, blocking does not incur a zero cost. This is because each thread ties up around 1MB of memory for as long as it lives and causes an ongoing administrative overhead for the CLR and operating system. For this reason, blocking can be troublesome in the context of heavily I/O-bound programs that need to handle hundreds or thousands of concurrent operations. Instead, such programs need to use a callback-based approach, rescinding their thread entirely while waiting. This is (in part) the purpose of the asynchronous patterns that we’ll discuss later.
Locking and thread safety are large topics. For a full discussion, see “Exclusive Locking” and “Locking and Thread Safety” in Chapter 22.
We can fix the previous example by obtaining an exclusive lock while reading and writing to the shared field. C# provides the lock
statement for just this purpose:
class ThreadSafe { static bool _done; static readonly object _locker = new object(); static void Main() { new Thread (Go).Start(); Go(); } static void Go() { lock (_locker) { if (!_done) { Console.WriteLine ("Done"); _done = true; } } } }
When two threads simultaneously contend a lock (which can be upon any reference-type object, in this case, _locker
), one thread waits, or blocks, until the lock becomes available. In this case, it ensures only one thread can enter its code block at a time, and “Done” will be printed just once. Code that’s protected in such a manner—from indeterminacy in a multithreaded context—is called thread-safe.
Even the act of autoincrementing a variable is not thread-safe: the expression x++
executes on the underlying processor as distinct read-increment-write operations. So, if two threads execute x++
at once outside a lock, the variable may end up getting incremented once rather than twice (or worse, x
could be torn, ending up with a bitwise-mixture of old and new content, under certain conditions).
Locking is not a silver bullet for thread safety—it’s easy to forget to lock around accessing a field, and locking can create problems of its own (such as deadlocking).
A good example of when you might use locking is around accessing a shared in-memory cache for frequently accessed database objects in an ASP.NET application. This kind of application is simple to get right, and there’s no chance of deadlocking. We give an example in “Thread Safety in Application Servers” in Chapter 22.
Sometimes you’ll want to pass arguments to the thread’s startup method. The easiest way to do this is with a lambda expression that calls the method with the desired arguments:
static void Main() { Thread t = new Thread ( () => Print ("Hello from t!") ); t.Start(); } static void Print (string message) { Console.WriteLine (message); }
With this approach, you can pass in any number of arguments to the method. You can even wrap the entire implementation in a multistatement lambda:
new Thread (() => { Console.WriteLine ("I'm running on another thread!"); Console.WriteLine ("This is so easy!"); }).Start();
Lambda expressions didn’t exist prior to C# 3.0. So you might also come across an old-school technique, which is to pass an argument into Thread
’s Start
method:
static void Main() { Thread t = new Thread (Print); t.Start ("Hello from t!"); } static void Print (object messageObj) { string message = (string) messageObj; // We need to cast here Console.WriteLine (message); }
This works because Thread
’s constructor is overloaded to accept either of two delegates:
public delegate void ThreadStart(); public delegate void ParameterizedThreadStart (object obj);
The limitation of ParameterizedThreadStart
is that it accepts only one argument. And because it’s of type object
, it usually needs to be cast.
As we saw, a lambda expression is the most convenient and powerful way to pass data to a thread. However, you must be careful about accidentally modifying captured variables after starting the thread. For instance, consider the following:
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start();
The output is nondeterministic! Here’s a typical result:
0223557799
The problem is that the i
variable refers to the same memory location throughout the loop’s lifetime. Therefore, each thread calls Console.Write
on a variable whose value may change as it is running! The solution is to use a temporary variable as follows:
for (int i = 0; i < 10; i++) { int temp = i; new Thread (() => Console.Write (temp)).Start(); }
Each of the digits 0 to 9 is then written exactly once. (The ordering is still undefined because threads may start at indeterminate times.)
This is analogous to the problem we described in “Captured Variables” in Chapter 8. The problem is just as much about C#’s rules for capturing variables in for
loops as it is about multithreading.
This problem also applies to foreach
loops prior to C# 5.
Variable temp
is now local to each loop iteration. Therefore, each thread captures a different memory location and there’s no problem. We can illustrate the problem in the earlier code more simply with the following example:
string text = "t1"; Thread t1 = new Thread ( () => Console.WriteLine (text) ); text = "t2"; Thread t2 = new Thread ( () => Console.WriteLine (text) ); t1.Start(); t2.Start();
Because both lambda expressions capture the same text variable, t2
is printed twice.
Any try
/catch
/finally
blocks in effect when a thread is created are of no relevance to the thread when it starts executing. Consider the following program:
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // We'll never get here! Console.WriteLine ("Exception!"); } } static void Go() { throw null; } // Throws a NullReferenceException
The try
/catch
statement in this example is ineffective, and the newly created thread will be encumbered with an unhandled NullReferenceException
. This behavior makes sense when you consider that each thread has an independent execution path.
The remedy is to move the exception handler into the Go
method:
public static void Main() { new Thread (Go).Start(); } static void Go() { try { ... throw null; // The NullReferenceException will get caught below ... } catch (Exception ex) { Typically log the exception, and/or signal another thread that we've come unstuck ... } }
You need an exception handler on all thread entry methods in production applications—just as you do (usually at a higher level, in the execution stack) on your main thread. An unhandled exception causes the whole application to shut down. With an ugly dialog box!
In writing such exception handling blocks, rarely would you ignore the error: typically, you’d log the details of the exception, and then perhaps display a dialog box allowing the user to automatically submit those details to your web server. You then might choose to restart the application, because it’s possible that an unexpected exception might leave your program in an invalid state.
In WPF, Windows Store, and Windows Forms applications, you can subscribe to “global” exception handling events, Application.DispatcherUnhandledException
and Application.ThreadException
, respectively. These fire after an unhandled exception in any part of your program that’s called via the message loop (this amounts to all code that runs on the main thread while the Application
is active). This is useful as a backstop for logging and reporting bugs (although it won’t fire for unhandled exceptions on non-UI threads that you create). Handling these events prevents the program from shutting down, although you may choose to restart the application to avoid the potential corruption of state that can follow from (or that led to) the unhandled exception.
AppDomain.CurrentDomain.UnhandledException
fires on any unhandled exception on any thread, but since CLR 2.0, the CLR forces application shutdown after your event handler completes. However, you can prevent shutdown by adding the following to your application configuration file:
<configuration> <runtime> <legacyUnhandledExceptionPolicy enabled="1" /> </runtime> </configuration>
This can be useful in programs that host multiple application domains (Chapter 24): if an unhandled exception occurs in a nondefault application domain, you can destroy and recreate the offending domain rather than restarting the whole application.
By default, threads you create explicitly are foreground threads. Foreground threads keep the application alive for as long as any one of them is running, whereas background threads do not. Once all foreground threads finish, the application ends, and any background threads still running abruptly terminate.
A thread’s foreground/background status has no relation to its priority (allocation of execution time).
You can query or change a thread’s background status using its IsBackground
property:
static void Main (string[] args) { Thread worker = new Thread ( () => Console.ReadLine() ); if (args.Length > 0) worker.IsBackground = true; worker.Start(); }
If this program is called with no arguments, the worker thread assumes foreground status and will wait on the ReadLine
statement for the user to press Enter. Meanwhile, the main thread exits, but the application keeps running because a foreground thread is still alive. On the other hand, if an argument is passed to Main()
, the worker is assigned background status, and the program exits almost immediately as the main thread ends (terminating the ReadLine
).
When a process terminates in this manner, any finally
blocks in the execution stack of background threads are circumvented. If your program employs finally
(or using
) blocks to perform cleanup work such as deleting temporary files, you can avoid this by explicitly waiting out such background threads upon exiting an application, either by joining the thread, or with a signaling construct (see “Signaling”). In either case, you should specify a timeout so you can abandon a renegade thread should it refuse to finish; otherwise your application will fail to close without the user having to enlist help from the Task Manager.
Foreground threads don’t require this treatment, but you must take care to avoid bugs that could cause the thread not to end. A common cause for applications failing to exit properly is the presence of active foreground threads.
A thread’s Priority
property determines how much execution time it gets relative to other active threads in the operating system, on the following scale:
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }
This becomes relevant when multiple threads are simultaneously active. Elevating a thread’s priority should be done with care as it can starve other threads. If you want a thread to have higher priority than threads in other processes, you must also elevate the process priority using the Process
class in System.Diagnostics
:
using (Process p = Process.GetCurrentProcess()) p.PriorityClass = ProcessPriorityClass.High;
This can work well for non-UI processes that do minimal work and need low latency (the ability to respond very quickly) in the work they do. With compute-hungry applications (particularly those with a user interface), elevating process priority can starve other processes, slowing down the entire computer.
Sometimes you need a thread to wait until receiving notification(s) from other thread(s). This is called signaling. The simplest signaling construct is ManualResetEvent
. Calling WaitOne
on a ManualResetEvent
blocks the current thread until another thread “opens” the signal by calling Set
. In the following example, we start up a thread that waits on a ManualResetEvent
. It remains blocked for two seconds until the main thread signals it:
var signal = new ManualResetEvent (false); new Thread (() => { Console.WriteLine ("Waiting for signal..."); signal.WaitOne(); signal.Dispose(); Console.WriteLine ("Got signal!"); }).Start(); Thread.Sleep(2000); signal.Set(); // "Open" the signal
After calling Set
, the signal remains open; it may be closed again by calling Reset
.
ManualResetEvent
is one of several signaling constructs provided by the CLR; we cover all of them in detail in Chapter 22.
In WPF, Windows Store, and Windows Forms applications, executing long-running operations on the main thread makes the application unresponsive, because the main thread also processes the message loop which performs rendering and handles keyboard and mouse events.
A popular approach is to start up “worker” threads for time-consuming operations. The code on a worker thread runs a time-consuming operation and then updates the UI when complete. However, all rich-client applications have a threading model whereby UI elements and controls can be accessed only from the thread that created them (typically the main UI thread). Violating this causes either unpredictable behavior or an exception to be thrown.
Hence when you want to update the UI from a worker thread, you must forward the request to the UI thread (the technical term is marshal). The low-level way to do this is as follows (later, we’ll discuss other solutions which build on these):
In WPF, call BeginInvoke
or Invoke
on the element’s Dispatcher
object.
In Windows Store apps, call RunAsync
or Invoke
on the Dispatcher
object.
In Windows Forms, call BeginInvoke
or Invoke
on the control.
All of these methods accept a delegate referencing the method you want to run. BeginInvoke/RunAsync
work by enqueuing the delegate to the UI thread’s message queue (the same queue that handles keyboard, mouse, and timer events). Invoke
does the same thing, but then blocks until the message has been read and processed by the UI thread. Because of this, Invoke
lets you get a return value back from the method. If you don’t need a return value, BeginInvoke
/RunAsync
are preferable in that they don’t block the caller and don’t introduce the possibility of deadlock (see “Deadlocks” in Chapter 22).
You can imagine that when you call Application.Run
, the following pseudocode executes:
while (!thisApplication.Ended
) {wait for something to appear in message queue
Got something: what kind of message is it?
Keyboard/mouse message -> fire an event handler
User BeginInvoke message -> execute delegate
User Invoke message -> execute delegate & post result
}
It’s this kind of loop that enables a worker thread to marshal a delegate for execution onto the UI thread.
To demonstrate, suppose that we have a WPF window that contains a text box called txtMessage
, whose content we wish a worker thread to update after performing a time-consuming task (which we will simulate by calling Thread.Sleep
). Here’s how we’d do it:
partial class MyWindow : Window { public MyWindow() { InitializeComponent(); new Thread (Work).Start(); } void Work() { Thread.Sleep (5000); // Simulate time-consuming task UpdateMessage ("The answer"); } void UpdateMessage (string message) { Action action = () => txtMessage.Text = message; Dispatcher.BeginInvoke (action); } }
Running this results in a responsive window appearing immediately. Five seconds later, it updates the textbox. The code is similar for Windows Forms, except that we call the (Form
’s) BeginInvoke
method instead:
void UpdateMessage (string message) { Action action = () => txtMessage.Text = message; this.BeginInvoke (action); }
In the System.ComponentModel
namespace, there’s an abstract class called SynchronizationContext
that enables the generalization of thread marshaling.
The rich-client APIs for mobile and desktop (Windows Store, WPF, and Windows Forms) each define and instantiate SynchronizationContext
subclasses which you can obtain via the static property SynchronizationContext.Current
(while running on a UI thread). Capturing this property let you later “post” to UI controls from a worker thread:
partial class MyWindow : Window { SynchronizationContext _uiSyncContext; public MyWindow() { InitializeComponent(); // Capture the synchronization context for the current UI thread: _uiSyncContext = SynchronizationContext.Current; new Thread (Work).Start(); } void Work() { Thread.Sleep (5000); // Simulate time-consuming task UpdateMessage ("The answer"); } void UpdateMessage (string message) { // Marshal the delegate to the UI thread: _uiSyncContext.Post (_ => txtMessage.Text = message, null); } }
This is useful because the same technique works with all rich-client User Interface APIs (SynchronizationContext
also has a ASP.NET specialization where it serves a more subtle role, ensuring that page processing events are processed sequentially following asynchronous operations, and to preserve the HttpContext
.)
Calling Post
is equivalent to calling BeginInvoke
on a Dispatcher
or Control
; there’s also a Send
method which is equivalent to Invoke
.
Framework 2.0 introduced the BackgroundWorker
class which used the SynchronizationContext
class to make the job of managing worker threads in rich-client applications a little easier. BackgroundWorker
has since been made redundant by the Tasks and asynchronous functions, which as we’ll see, also leverage SynchronizationContext
.
Whenever you start a thread, a few hundred microseconds are spent organizing such things as a fresh local variable stack. The thread pool cuts this overhead by having a pool of pre-created recyclable threads. Thread pooling is essential for efficient parallel programming and fine-grained concurrency; it allows short operations to run without being overwhelmed with the overhead of thread startup.
There are a few things to be wary of when using pooled threads:
You cannot set the Name
of a pooled thread, making debugging more difficult (although you can attach a description when debugging in Visual Studio’s Threads window).
Blocking pooled threads can degrade performance (see “Hygiene in the thread pool”).
You are free to change the priority of a pooled thread—it will be restored to normal when released back to the pool.
You can query if you’re currently executing on a pooled thread via the property Thread.CurrentThread.IsThreadPoolThread
.
The easiest way to explicitly run something on a pooled thread is to use Task.Run
(we’ll cover this in more detail in the following section):
// Task is in System.Threading.Tasks Task.Run (() => Console.WriteLine ("Hello from the thread pool"));
As tasks didn’t exist prior to Framework 4.0, a common alternative is to call ThreadPool.QueueUserWorkItem
:
ThreadPool.QueueUserWorkItem (notUsed => Console.WriteLine ("Hello"));
The following use the thread pool implicitly:
WCF, Remoting, ASP.NET, and ASMX Web Services application servers
System.Timers.Timer
and System.Threading.Timer
The parallel programming constructs that we describe in Chapter 23
The (now redundant) BackgroundWorker
class
Asynchronous delegates (also now redundant)
The thread pool serves another function, which is to ensure that a temporary excess of compute-bound work does not cause CPU oversubscription. Oversubscription is the condition of there being more active threads than CPU cores, with the operating system having to time-slice threads. Oversubscription hurts performance because time-slicing requires expensive context switches and can invalidate the CPU caches that have become essential in delivering performance to modern processors.
The CLR avoids oversubscription in the thread pool by queuing tasks and throttling their startup. It begins by running as many concurrent tasks as there are hardware cores, and then tunes the level of concurrency via a hill-climbing algorithm, continually adjusting the workload in a particular direction. If throughput improves, it continues in the same direction (otherwise it reverses). This ensures that it always tracks the optimal performance curve—even in the face of competing process activity on the computer.
The CLR’s strategy works best if two conditions are met:
Work items are mostly short-running (<250 ms, or ideally <100 ms), so that the CLR has plenty of opportunities to measure and adjust.
Jobs that spend most of their time blocked do not dominate the pool.
Blocking is troublesome because it gives the CLR the false idea that it’s loading up the CPU. The CLR is smart enough to detect and compensate (by injecting more threads into the pool), although this can make the pool vulnerable to subsequent oversubscription. It also may introduce latency, as the CLR throttles the rate at which it injects new threads, particularly early in an application’s life (more so on client operating systems where it favors lower resource consumption).
Maintaining good hygiene in the thread pool is particularly relevant when you want to fully utilize the CPU (e.g., via the parallel programming APIs in Chapter 23).
A thread is a low-level tool for creating concurrency, and as such it has limitations. In particular:
While it’s easy to pass data into a thread that you start, there’s no easy way to get a “return value” back from a thread that you Join
. You have to set up some kind of shared field. And if the operation throws an exception, catching and propagating that exception is equally painful.
You can’t tell a thread to start something else when it’s finished; instead you must Join
it (blocking your own thread in the process).
These limitations discourage fine-grained concurrency; in other words, they make it hard to compose larger concurrent operations by combining smaller ones (something essential for the asynchronous programming that we’ll look at in following sections). This in turn leads to greater reliance on manual synchronization (locking, signaling, and so on) and the problems that go with it.
The direct use of threads also has performance implications that we discussed in “The Thread Pool”. And should you need to run hundreds or thousands of concurrent I/O-bound operations, a thread-based approach consumes hundreds or thousands of MB of memory purely in thread overhead.
The Task
class helps with all of these problems. Compared to a thread, a Task
is higher-level abstraction—it represents a concurrent operation that may or may not be backed by a thread. Tasks are compositional (you can chain them together through the use of continuations). They can use the thread pool to lessen startup latency, and with a TaskCompletionSource
, they can leverage a callback approach that avoids threads altogether while waiting on I/O-bound operations.
The Task
types were introduced in Framework 4.0 as part of the parallel programming library. However they have since been enhanced (through the use of awaiters) to play equally well in more general concurrency scenarios and are backing types for C#’s asynchronous functions.
In this section, we’ll ignore the features of tasks that are aimed specifically at parallel programming and cover them instead in Chapter 23.
From Framework 4.5, the easiest way to start a Task
backed by a thread is with the static method Task.Run
(the Task
class is in the System.Threading.Tasks
namespace). Simply pass in an Action
delegate:
Task.Run (() => Console.WriteLine ("Foo"));
The Task.Run
method was introduced in Framework 4.5. In Framework 4.0, you can accomplish the same thing by calling Task.Factory.StartNew
. (The former is mostly a shortcut for the latter.)
Tasks use pooled threads by default, which are background threads. This means that when the main thread ends, so do any tasks that you create. Hence, to run these examples from a Console application, you must block the main thread after starting the task (for instance, by Wait
ing the task or by calling Console.ReadLine
):
static void Main() { Task.Run (() => Console.WriteLine ("Foo")); Console.ReadLine(); }
In the book’s LINQPad companion samples, Console.ReadLine
is omitted because the LINQPad process keeps background threads alive.
Calling Task.Run
in this manner is similar to starting a thread as follows (except for the thread pooling implications that we’ll discuss shortly):
new Thread (() => Console.WriteLine ("Foo")).Start();
Task.Run
returns a Task
object that we can use to monitor its progress, rather like a Thread
object. (Notice, however, that we didn’t call Start
after calling Task.Run
because this method creates “hot” tasks; you can instead use Task
’s constructor to create “cold” tasks, although this is rarely done in practice.)
You can track a task’s execution status via its Status
property.
Calling Wait
on a task blocks until it completes and is the equivalent of calling Join
on a thread:
Task task = Task.Run (() => { Thread.Sleep (2000); Console.WriteLine ("Foo"); }); Console.WriteLine (task.IsCompleted); // False task.Wait(); // Blocks until task is complete
Wait
lets you optionally specify a timeout and a cancellation token to end the wait early (see “Cancellation”).
By default, the CLR runs tasks on pooled threads, which is ideal for short-running compute-bound work. For longer-running and blocking operations (such as our preceding example), you can prevent use of a pooled thread as follows:
Task task = Task.Factory.StartNew (() => ..., TaskCreationOptions.LongRunning);
Running one long-running task on a pooled thread won’t cause trouble; it’s when you run multiple long-running tasks in parallel (particularly ones that block) that performance can suffer. And in that case, there are usually better solutions than TaskCreationOptions.LongRunning
:
If the tasks are I/O-bound, TaskCompletionSource
and asynchronous functions let you implement concurrency with callbacks (continuations) instead of threads.
If the tasks are compute-bound, a producer/consumer queue lets you throttle the concurrency for those tasks, avoiding starvation for other threads and processes (see “Writing a Producer/Consumer Queue” in Chapter 23).
Task
has a generic subclass called Task<TResult>
that allows a task to emit a return value. You can obtain a Task<TResult>
by calling Task.Run
with a Func<TResult>
delegate (or a compatible lambda expression) instead of an Action
:
Task<int> task = Task.Run (() => { Console.WriteLine ("Foo"); return 3; }); // ...
You can obtain the result later by querying the Result
property. If the task hasn’t yet finished, accessing this property will block the current thread until the task finishes:
int result = task.Result; // Blocks if not already finished Console.WriteLine (result); // 3
In the following example, we create a task that uses LINQ to count the number of prime numbers in the first three million (+2) integers:
Task<int> primeNumberTask = Task.Run (() => Enumerable.Range (2, 3000000).Count (n => Enumerable.Range (2, (int)Math.Sqrt(n)-1).All (i => n % i > 0))); Console.WriteLine ("Task running..."); Console.WriteLine ("The answer is " + primeNumberTask.Result);
This writes “Task running...”, and then a few seconds later, writes the answer of 216815.
Task<TResult>
can be thought of as a “future,” in that it encapsulates a Result
that becomes available later in time.
Interestingly, when Task
and Task<TResult>
first debuted in an early CTP, the latter was actually called Future<TResult>
.
Unlike with threads, tasks conveniently propagate exceptions. So, if the code in your task throws an unhandled exception (in other words, if your task faults), that exception is automatically rethrown to whoever calls Wait()
—or accesses the Result
property of a Task<TResult>
:
// Start a Task that throws a NullReferenceException: Task task = Task.Run (() => { throw null; }); try { task.Wait(); } catch (AggregateException aex) { if (aex.InnerException is NullReferenceException) Console.WriteLine ("Null!"); else throw; }
(The CLR wraps the exception in an AggregateException
in order to play well with parallel programming scenarios; we discuss this in Chapter 23.)
You can test for a faulted task without rethrowing the exception via the IsFaulted
and IsCanceled
properties of the Task
. If both properties return false
, no error occurred; if IsCanceled
is true
, an OperationCanceledException
was thrown for that task (see “Cancellation”); if IsFaulted
is true
, another type of exception was thrown, and the Exception
property will indicate the error.
With autonomous “set-and-forget” tasks (those for which you don’t rendezvous via Wait()
or Result
or a continuation that does the same), it’s good practice to explicitly exception-handle the task code to avoid silent failure, just as you would with a thread.
Unhandled exceptions on autonomous tasks are called unobserved exceptions, and in CLR 4.0, they would actually terminate your program (the CLR would rethrow the exception on the finalizer thread when the task dropped out of scope and was garbage collected). This was helpful in indicating that a problem had occurred that you might not have been aware of; however the timing of the error could be deceptive in that the garbage collector can lag significantly behind the offending task. Hence, when it was discovered that this behavior complicated certain patterns of asynchrony (see “Parallelism” and “WhenAll”), it was dropped in CLR 4.5.
Ignoring exceptions is fine when an exception solely indicates a failure to obtain a result that you’re no longer interested in. For example, if a user cancels a request to download a web page, we wouldn’t care if it turns out that the web page didn’t exist.
Ignoring exceptions is problematic when an exception indicates a bug in your program, for two reasons:
The bug may have left your program in an invalid state.
More exceptions may occur later as a result of the bug, and failure to log the initial error can make diagnosis difficult.
You can subscribe to unobserved exceptions at a global level via the static event TaskScheduler.UnobservedTaskException
; handling this event and logging the error can make good sense.
There are a couple of interesting nuances on what counts as unobserved:
A continuation says to a task, “When you’ve finished, continue by doing something else.” A continuation is usually implemented by a callback that executes once upon completion of an operation. There are two ways to attach a continuation to a task. The first was introduced in Framework 4.5 and is particularly significant because it’s used by C#’s asynchronous functions, as we’ll see soon. We can demonstrate it with the prime number counting task that we wrote a short while ago in “Returning values”:
Task<int> primeNumberTask = Task.Run (() => Enumerable.Range (2, 3000000).Count (n => Enumerable.Range (2, (int)Math.Sqrt(n)-1).All (i => n % i > 0))); var awaiter = primeNumberTask.GetAwaiter(); awaiter.OnCompleted (() => { int result = awaiter.GetResult(); Console.WriteLine (result); // Writes result });
Calling GetAwaiter
on the task returns an awaiter object whose OnCompleted
method tells the antecedent task (primeNumberTask
) to execute a delegate when it finishes (or faults). It’s valid to attach a continuation to an already-completed task, in which case the continuation will be scheduled to execute right away.
An awaiter is any object that exposes the two methods that we’ve just seen (OnCompleted
and GetResult
), and a Boolean property called IsCompleted
. There’s no interface or base class to unify all of these members (although OnCompleted
is part of the interface INotifyCompletion
). We’ll explain the significance of the pattern in “Asynchronous Functions in C# ”.
If an antecedent task faults, the exception is rethrown when the continuation code calls awaiter.GetResult()
. Rather than calling GetResult
, we could simply access the Result
property of the antecedent. The benefit of calling GetResult
is that if the antecedent faults, the exception is thrown directly without being wrapped in AggregateException
, allowing for simpler and cleaner catch
blocks.
For nongeneric tasks, GetResult()
has a void return value. Its useful function is then solely to rethrow exceptions.
If a synchronization context is present, OnCompleted
automatically captures it and posts the continuation to that context. This is very useful in rich-client applications, as it bounces the continuation back to the UI thread. In writing libraries, however, it’s not usually desirable because the relatively expensive UI-thread-bounce should occur just once upon leaving the library, rather than between method calls. Hence you can defeat it the ConfigureAwait
method:
var awaiter = primeNumberTask.ConfigureAwait (false).GetAwaiter();
If no synchronization context is present—or you use ConfigureAwait(false)
—the continuation will (in general) execute on the same thread as the antecedent, avoiding unnecessary overhead.
The other way to attach a continuation is by calling the task’s ContinueWith
method:
primeNumberTask.ContinueWith (antecedent => { int result = antecedent.Result; Console.WriteLine (result); // Writes 123 });
ContinueWith
itself returns a Task
, which is useful if you want to attach further continuations. However, you must deal directly with AggregateException
if the task faults and write extra code to marshal the continuation in UI applications (see “Task Schedulers” in Chapter 23). And in non-UI contexts, you must specify TaskContinuationOptions.ExecuteSynchronously
if you want the continuation to execute on the same thread; otherwise it will bounce to the thread pool. ContinueWith
is particularly useful in parallel programming scenarios; we cover it in detail in “Continuations” in Chapter 23.
We’ve seen how Task.Run
creates a task that runs a delegate on a pooled (or non-pooled) thread. Another way to create a task is with TaskCompletionSource
.
TaskCompletionSource
lets you create a task out of any operation that starts and finishes some time later. It works by giving you a “slave” task that you manually drive—by indicating when the operation finishes or faults. This is ideal for I/O-bound work: you get all the benefits of tasks (with their ability to propagate return values, exceptions, and continuations) without blocking a thread for the duration of the operation.
To use TaskCompletionSource
, you simply instantiate the class. It exposes a Task
property that returns a task upon which you can wait and attach continuations—just as with any other task. The task, however, is controlled entirely by the TaskCompletionSource
object via the following methods:
public class TaskCompletionSource<TResult> { public void SetResult (TResult result); public void SetException (Exception exception); public void SetCanceled(); public bool TrySetResult (TResult result); public bool TrySetException (Exception exception); public bool TrySetCanceled(); public bool TrysetCanceled (CancellationToken cancellationToken); ... }
Calling any of these methods signals the task, putting it into a completed, faulted, or canceled state (we’ll cover the latter in the section “Cancellation”). You’re supposed to call one of these methods exactly once: if called again, SetResult
, SetException
, or SetCanceled
will throw an exception, whereas the Try*
methods return false
.
The following example prints 42 after waiting for five seconds:
var tcs = new TaskCompletionSource<int>(); new Thread (() => { Thread.Sleep (5000); tcs.SetResult (42); }) { IsBackground = true } .Start(); Task<int> task = tcs.Task; // Our "slave" task. Console.WriteLine (task.Result); // 42
With TaskCompletionSource
, we can write our own Run
method:
Task<TResult> Run<TResult> (Func<TResult> function) { var tcs = new TaskCompletionSource<TResult>(); new Thread (() => { try { tcs.SetResult (function()); } catch (Exception ex) { tcs.SetException (ex); } }).Start(); return tcs.Task; } ... Task<int> task = Run (() => { Thread.Sleep (5000); return 42; });
Calling this method is equivalent to calling Task.Factory.StartNew
with the TaskCreationOptions.LongRunning
option to request a nonpooled thread.
The real power of TaskCompletionSource
is in creating tasks that don’t tie up threads. For instance, consider a task that waits for five seconds and then returns the number 42. We can write this without a thread by using the Timer
class, which with the help of the CLR (and in turn, the operating system) fires an event in x milliseconds (we revisit timers in Chapter 22):
Task<int> GetAnswerToLife() { var tcs = new TaskCompletionSource<int>(); // Create a timer that fires once in 5000 ms: var timer = new System.Timers.Timer (5000) { AutoReset = false }; timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult (42); }; timer.Start(); return tcs.Task; }
Hence our method returns a task that completes five seconds later, with a result of 42. By attaching a continuation to the task, we can write its result without blocking any thread:
var awaiter = GetAnswerToLife().GetAwaiter(); awaiter.OnCompleted (() => Console.WriteLine (awaiter.GetResult()));
We could make this more useful and turn it into a general-purpose Delay
method by parameterizing the delay time and getting rid of the return value. This means having it return a Task
instead of a Task<int>
. However, there’s no nongeneric version of TaskCompletionSource
, which means we can’t directly create a nongeneric Task
. The workaround is simple: since Task<TResult>
derives from Task
, we create a TaskCompletionSource<anything>
and then implicitly convert the Task<anything>
that it gives you into a Task
, like this:
var tcs = new TaskCompletionSource<object>(); Task task = tcs.Task;
Now we can write our general-purpose Delay
method:
Task Delay (int milliseconds) { var tcs = new TaskCompletionSource<object>(); var timer = new System.Timers.Timer (milliseconds) { AutoReset = false }; timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult (null); }; timer.Start(); return tcs.Task; }
Here’s how we can use it to write “42” after five seconds:
Delay (5000).GetAwaiter().OnCompleted (() => Console.WriteLine (42));
Our use of TaskCompletionSource
without a thread means that a thread is engaged only when the continuation starts, five seconds later. We can demonstrate this by starting 10,000 of these operations at once without error or excessive resource consumption:
for (int i = 0; i < 10000; i++) Delay (5000).GetAwaiter().OnCompleted (() => Console.WriteLine (42));
Timers fire their callbacks on pooled threads, so after 5 seconds, the thread pool will receive 10,000 requests to call SetResult(null)
on a TaskCompletionSource
. If the requests arrive faster than they can be processed, the thread pool will respond by enqueuing and then processing them at the optimum level of parallelism for the CPU. This is ideal if the thread-bound jobs are short-running, which is true in this case: the thread-bound job is merely the call to SetResult
plus either the action of posting the continuation to the synchronization context (in a UI application) or otherwise the continuation itself (Console.WriteLine(42)
).
In demonstrating TaskCompletionSource
, we ended up writing asynchronous methods. In this section, we’ll define exactly what asynchronous operations are and explain how this leads to asynchronous programming.
A synchronous operation does its work before returning to the caller.
An asynchronous operation does (most or all of) its work after returning to the caller.
The majority of methods that you write and call are synchronous. An example is List<T>.Add
, or Console.WriteLine
, or Thread.Sleep
. Asynchronous methods are less common and initiate concurrency because work continues in parallel to the caller. Asynchronous methods typically return quickly (or immediately) to the caller; hence they are also called nonblocking methods.
Most of the asynchronous methods that we’ve seen so far can be described as general-purpose methods:
Thread.Start
Task.Run
Methods that attach continuations to tasks
In addition, some of the methods that we discussed in “Synchronization Contexts” (Dispatcher.BeginInvoke
, Control.BeginInvoke
and SynchronizationContext.Post
) are asynchronous, as are the methods that we wrote in the section, “TaskCompletionSource”, including Delay
.
The principle of asynchronous programming is that you write long-running (or potentially long-running) functions asynchronously. This is in contrast to the conventional approach of writing long-running functions synchronously and then calling those functions from a new thread or task to introduce concurrency as required.
The difference with the asynchronous approach is that concurrency is initiated inside the long-running function, rather than from outside the function. This has two benefits:
I/O-bound concurrency can be implemented without tying up threads (as we demonstrated in “TaskCompletionSource”), improving scalability and efficiency.
Rich-client applications end up with less code on worker threads, simplifying thread safety.
This, in turn, leads to two distinct uses for asynchronous programming. The first is writing (typically server-side) applications that deal efficiently with a lot of concurrent I/O. The challenge here is not thread-safety (as there’s usually minimal shared state) but thread efficiency; in particular, not consuming a thread per network request. Hence in this context, it’s only I/O-bound operations that benefit from asynchrony.
The second use is to simplify thread-safety in rich-client applications. This is particularly relevant as a program grows in size, because to deal with complexity, we typically refactor larger methods into smaller ones, resulting in chains of methods that call one another (call graphs).
With a traditional synchronous call graph, if any operation within the graph is long-running, we must run the entire call graph on a worker thread to maintain a responsive UI. Hence, we end up with a single concurrent operation that spans many methods (coarse-grained concurrency), and this requires considering thread-safety for every method in the graph.
With an asynchronous call graph, we need not start a thread until it’s actually needed, typically low in the graph (or not at all in the case of I/O-bound operations). All other methods can run entirely on the UI thread, with much-simplified thread-safety. This results in fine-grained concurrency—a sequence of small concurrent operations, in between which execution bounces to the UI thread.
To benefit from this, both I/O- and compute-bound operations need to be written asynchronously; a good rule of thumb is to include anything that might take longer than 50 ms.
(On the flipside, excessively fine-grained asynchrony can hurt performance, because asynchronous operations incur an overhead—see “Optimizations”.)
In this chapter, we’ll focus mostly on the rich-client scenario, which is the more complex of the two. In Chapter 16, we give two examples that illustrate the I/O-bound scenario (see “Concurrency with TCP” and “Writing an HTTP Server”).
The Windows store (and Silverlight) .NET profiles encourage asynchronous programming to the point where synchronous versions of some long-running methods are not even exposed. Instead, you get asynchronous methods that return tasks (or objects that can be converted into tasks via the AsTask
extension method).
Tasks are ideally suited to asynchronous programming because they support continuations which are essential for asynchrony (consider the Delay
method that we wrote previously in “TaskCompletionSource”). In writing Delay
, we used TaskCompletionSource
, which is a standard way to implement “bottom-level” I/O-bound asynchronous methods.
For compute-bound methods, we use Task.Run
to initiate thread-bound concurrency. Simply by returning the task to the caller, we create an asynchronous method. What distinguishes asynchronous programming is that we aim to do so lower in the call graph, so that in rich-client applications, higher-level methods can remain on the UI thread and access controls and shared state without thread-safety issues. To illustrate, consider the following method which computes and counts prime numbers, using all available cores (we discuss ParallelEnumerable
in Chapter 23):
int GetPrimesCount (int start, int count) { return ParallelEnumerable.Range (start, count).Count (n => Enumerable.Range (2, (int)Math.Sqrt(n)-1).All (i => n % i > 0)); }
The details of how this works are unimportant; what matters is that it can take a while to run. We can demonstrate this by writing another method to call it:
void DisplayPrimeCounts() { for (int i = 0; i < 10; i++) Console.WriteLine (GetPrimesCount (i*1000000 + 2, 1000000) + " primes between " + (i*1000000) + " and " + ((i+1)*1000000-1)); Console.WriteLine ("Done!"); }
with the following output:
78498 primes between 0 and 999999 70435 primes between 1000000 and 1999999 67883 primes between 2000000 and 2999999 66330 primes between 3000000 and 3999999 65367 primes between 4000000 and 4999999 64336 primes between 5000000 and 5999999 63799 primes between 6000000 and 6999999 63129 primes between 7000000 and 7999999 62712 primes between 8000000 and 8999999 62090 primes between 9000000 and 9999999
Now we have a call graph, with DisplayPrimeCounts
calling GetPrimesCount
. The former uses Console.WriteLine
for simplicity, although in reality, it would more likely be updating UI controls in a rich-client application, as we’ll demonstrate later. We can initiate coarse-grained concurrency for this call graph as follows:
Task.Run (() => DisplayPrimeCounts());
With a fine-grained asynchronous approach, we instead start by writing an asynchronous version of GetPrimesCount
:
Task<int> GetPrimesCountAsync (int start, int count) { return Task.Run (() => ParallelEnumerable.Range (start, count).Count (n => Enumerable.Range (2, (int) Math.Sqrt(n)-1).All (i => n % i > 0))); }
Now we must modify DisplayPrimeCounts
so that it calls GetPrimesCountAsync
. This is where C#’s new await
and async
keywords come into play, because to do so otherwise is trickier than it sounds. If we simply modify the loop as follows:
for (int i = 0; i < 10; i++) { var awaiter = GetPrimesCountAsync (i*1000000 + 2, 1000000).GetAwaiter(); awaiter.OnCompleted (() => Console.WriteLine (awaiter.GetResult() + " primes between... ")); } Console.WriteLine ("Done");
then the loop will rapidly spin through 10 iterations (the methods being nonblocking), and all 10 operations will execute in parallel (followed by a premature “Done”).
Executing these tasks in parallel is undesirable in this case because their internal implementations are already parallelized; it will only make us wait longer to see the first results (and muck up the ordering).
There is a much more common reason, however, for needing to serialize the execution of tasks, which is that Task B depends on the result of Task A. For example, in fetching a web page, a DNS lookup must precede the HTTP request.
To get them running sequentially, we must trigger the next loop iteration from the continuation itself. This means eliminating the for
loop and resorting to a recursive call in the continuation:
void DisplayPrimeCounts() { DisplayPrimeCountsFrom (0); } void DisplayPrimeCountsFrom (int i) { var awaiter = GetPrimesCountAsync (i*1000000 + 2, 1000000).GetAwaiter(); awaiter.OnCompleted (() => { Console.WriteLine (awaiter.GetResult() + " primes between..."); if (++i < 10) DisplayPrimeCountsFrom (i); else Console.WriteLine ("Done"); }); }
It gets even worse if we want to make DisplayPrimesCount
itself asynchronous, returning a task that it signals upon completion. To accomplish this requires creating a TaskCompletionSource
:
Task DisplayPrimeCountsAsync() { var machine = new PrimesStateMachine(); machine.DisplayPrimeCountsFrom (0); return machine.Task; } class PrimesStateMachine { TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>(); public Task Task { get { return _tcs.Task; } } public void DisplayPrimeCountsFrom (int i) { var awaiter = GetPrimesCountAsync (i*1000000+2, 1000000).GetAwaiter(); awaiter.OnCompleted (() => { Console.WriteLine (awaiter.GetResult()); if (i++ < 10) DisplayPrimeCountsFrom (i); else { Console.WriteLine ("Done"); _tcs.SetResult (null); } }); } }
Fortunately, C#’s asynchronous functions do all of this work for us. With the async
and await
keywords, we need only write this:
async Task DisplayPrimeCountsAsync() { for (int i = 0; i < 10; i++) Console.WriteLine (await GetPrimesCountAsync (i*1000000 + 2, 1000000) + " primes between " + (i*1000000) + " and " + ((i+1)*1000000-1)); Console.WriteLine ("Done!"); }
Hence async
and await
are essential for implementing asynchrony without excessive complexity. Let’s now see how these keywords work.
Another way of looking at the problem is that imperative looping constructs (for
, foreach
and so on), do not mix well with continuations because they rely on the current local state of the method (“How many more times is this loop going to run?”).
While the async
and await
keywords offer one solution, it’s sometimes possible to solve it in another way by replacing the imperative looping constructs with the functional equivalent (in other words, LINQ queries). This is the basis of Reactive Framework (Rx) and can be a good option when you want to execute query operators over the result—or combine multiple sequences. The price to pay is that to avoid blocking, Rx operates over push-based sequences, which can be conceptually tricky.
C# 5.0 introduced the async
and await
keywords. These keywords let you write asynchronous code that has the same structure and simplicity as synchronous code and eliminates the “plumbing” of asynchronous programming.
The await
keyword simplifies the attaching of continuations. Starting with a basic scenario, the compiler expands:
var result = await expression; statement(s);
into something functionally similar to:
var awaiter = expression.GetAwaiter(); awaiter.OnCompleted (() => { var result = awaiter.GetResult(); statement(s); });
The compiler also emits code to short-circuit the continuation in case of synchronous completion (see “Optimizations”) and to handle various nuances that we’ll pick up in later sections.
To demonstrate, let’s revisit the asynchronous method that we wrote previously that computes and counts prime numbers:
Task<int> GetPrimesCountAsync (int start, int count) { return Task.Run (() => ParallelEnumerable.Range (start, count).Count (n => Enumerable.Range (2, (int)Math.Sqrt(n)-1).All (i => n % i > 0))); }
With the await
keyword, we can call it as follows:
int result = await GetPrimesCountAsync (2, 1000000); Console.WriteLine (result);
In order to compile, we need to add the async
modifier to the containing method:
async void DisplayPrimesCount() { int result = await GetPrimesCountAsync (2, 1000000); Console.WriteLine (result); }
The async
modifier tells the compiler to treat await
as a keyword rather than an identifier should an ambiguity arise within that method (this ensures that code written prior to C# 5 that might use await
as an identifier will still compile without error). The async
modifier can be applied only to methods (and lambda expressions) that return void
or (as we’ll see later) a Task
or Task<TResult>
.
The async
modifier is similar to the unsafe
modifier in that it has no effect on a method’s signature or public metadata; it affects only what happens inside the method. For this reason, it makes no sense to use async
in an interface. However it is legal, for instance, to introduce async
when overriding a non-async
virtual method, as long as you keep the signature the same.
Methods with the async
modifier are called asynchronous functions, because they themselves are typically asynchronous. To see why, let’s look at how execution proceeds through an asynchronous function.
Upon encountering an await
expression, execution (normally) returns to the caller—rather like with yield return
in an iterator. But before returning, the runtime attaches a continuation to the awaited task, ensuring that when the task completes, execution jumps back into the method and continues where it left off. If the task faults, its exception is rethrown, otherwise its return value is assigned to the await
expression. We can summarize everything we just said by looking at the logical expansion of the preceding asynchronous method:
void DisplayPrimesCount() { var awaiter = GetPrimesCountAsync (2, 1000000).GetAwaiter(); awaiter.OnCompleted (() => { int result = awaiter.GetResult(); Console.WriteLine (result); }); }
The expression upon which you await
is typically a task; however any object with a GetAwaiter
method that returns an awaitable object (implementing INotifyCompletion.OnCompleted
and with an appropriately typed GetResult
method and a bool IsCompleted
property) will satisfy the compiler.
Notice that our await
expression evaluates to an int
type; this is because the expression that we awaited was a Task<int>
(whose GetAwaiter().GetResult()
method returns an int
).
Awaiting a nongeneric task is legal and generates a void expression:
await Task.Delay (5000); Console.WriteLine ("Five seconds passed!");
The real power of await
expressions is that they can appear almost anywhere in code. Specifically, an await
expression can appear in place of any expression (within an asynchronous function) except for inside a lock
expression, unsafe
context or an executable’s entry point (main method).
In the following example, we await
inside a loop:
async void DisplayPrimeCounts() { for (int i = 0; i < 10; i++) Console.WriteLine (await GetPrimesCountAsync (i*1000000+2, 1000000)); }
Upon first executing GetPrimesCountAsync
, execution returns to the caller by virtue of the await
expression. When the method completes (or faults), execution resumes where it left off, with the values of local variables and loop counters preserved.
Without the await
keyword, the simplest equivalent might be the example we wrote in “Why Language Support Is Important”. The compiler, however, takes the more general strategy of refactoring such methods into state machines (rather like it does with iterators).
The compiler relies on continuations (via the awaiter pattern) to resume execution after an await
expression. This means that if running on the UI thread of a rich-client application, the synchronization context ensures execution resumes on the same thread. Otherwise, execution resumes on whatever thread the task finished on. The change of thread does not affect the order of execution and is of little consequence unless you’re somehow relying on thread affinity, perhaps through the use of thread-local storage (see “Thread-Local Storage” in Chapter 22). It’s rather like touring a city and hailing taxis to get from one destination to another. With a synchronization context, you’ll always get the same taxi; with no synchronization context, you’ll usually get a different taxi each time. In either case, though, the journey is the same.
We can demonstrate asynchronous functions in a more practical context by writing a simple UI that remains responsive while calling a compute-bound method. Let’s start with a synchronous solution:
class TestUI : Window { Button _button = new Button { Content = "Go" }; TextBlock _results = new TextBlock(); public TestUI() { var panel = new StackPanel(); panel.Children.Add (_button); panel.Children.Add (_results); Content = panel; _button.Click += (sender, args) => Go(); } void Go() { for (int i = 1; i < 5; i++) _results.Text += GetPrimesCount (i * 1000000, 1000000) + " primes between " + (i*1000000) + " and " + ((i+1)*1000000-1) + Environment.NewLine; } int GetPrimesCount (int start, int count) { return ParallelEnumerable.Range (start, count).Count (n => Enumerable.Range (2, (int) Math.Sqrt(n)-1).All (i => n % i > 0)); } }
Upon pressing the “Go” button, the application becomes unresponsive for the time it takes to execute the compute-bound code. There are two steps in asynchronizing this; the first is to switch to the asynchronous version of GetPrimesCount
that we used in previous examples:
Task<int> GetPrimesCountAsync (int start, int count) { return Task.Run (() => ParallelEnumerable.Range (start, count).Count (n => Enumerable.Range (2, (int) Math.Sqrt(n)-1).All (i => n % i > 0))); }
The second step is to modify Go
to call GetPrimesCountAsync
:
async void Go() { _button.IsEnabled = false; for (int i = 1; i < 5; i++) _results.Text += await GetPrimesCountAsync (i * 1000000, 1000000) + " primes between " + (i*1000000) + " and " + ((i+1)*1000000-1) + Environment.NewLine; _button.IsEnabled = true; }
This illustrates the simplicity of programming with asynchronous functions: you program as you would synchronously but call asynchronous functions instead of blocking functions and await
them. Only the code within GetPrimesCountAsync
runs on a worker thread; the code in Go
“leases” time on the UI thread. We could say that Go
executes pseudoconcurrently to the message loop (in that its execution is interspersed with other events that the UI thread processes). With this pseudoconcurrency, the only point at which preemption can occur is during an await
. This simplifies thread-safety: in our case, the only problem that this could cause is reentrancy (clicking the button again while it’s running, which we avoid by disabling the button). True concurrency occurs lower in the call stack, inside code called by Task.Run
. To benefit from this model, truly concurrent code avoids accessing shared state or UI controls.
To give another example, suppose that instead of calculating prime numbers, we want to download several web pages and sum their lengths. Framework 4.5 (and later) exposes numerous task-returning asynchronous methods, one of which is the WebClient
class in System.Net
. The DownloadDataTaskAsync
method asynchronously downloads a URI to a byte array, returning a Task<byte[]>
, so by awaiting it, we get a byte[]
. Let’s now rewrite our Go
method:
async void Go() { _button.IsEnabled = false; string[] urls = "www.albahari.com www.oreilly.com www.linqpad.net".Split(); int totalLength = 0; try { foreach (string url in urls) { var uri = new Uri ("http://" + url); byte[] data = await new WebClient().DownloadDataTaskAsync (uri); _results.Text += "Length of " + url + " is " + data.Length + Environment.NewLine; totalLength += data.Length; } _results.Text += "Total length: " + totalLength; } catch (WebException ex) { _results.Text += "Error: " + ex.Message; } finally { _button.IsEnabled = true; } }
Again, this mirrors how we’d write it synchronously—including the use of catch
and finally
blocks. Even though execution returns to the caller after the first await
, the finally
block does not execute until the method has logically completed (by virtue of all its code executing—or an early return
or unhandled exception).
It can be helpful to consider exactly what’s happening underneath. First, we need to revisit the pseudocode that runs the message loop on the UI thread:
Set synchronization context for this thread to WPF sync context while (!thisApplication.Ended) { wait for something to appear in message queue Got something: what kind of message is it? Keyboard/mouse message -> fire an event handler User BeginInvoke/Invoke message -> execute delegate }
Event handlers that we attach to UI elements execute via this message loop. When our Go
method runs, execution proceeds as far as the await
expression and then returns to the message loop (freeing the UI to respond to further events). The compiler’s expansion of await
ensures that before returning, however, a continuation is set up such that execution resumes where it left off upon completion of the task. And because we awaited on a UI thread, the continuation posts to the synchronization context which executes it via the message loop, keeping our entire Go
method executing pseudoconcurrently on the UI thread. True (I/O-bound) concurrency occurs within the implementation of DownloadDataTaskAsync
.
Asynchronous programming was difficult prior to C# 5, not only because there was no language support, but because the .NET Framework exposed asynchronous functionality through clumsy patterns called the EAP and the APM (see “Obsolete Patterns”), rather than task-returning methods.
The popular workaround was coarse-grained concurrency (in fact, there was even a type called BackgroundWorker
to help with that). Returning to our original synchronous example with GetPrimesCount
, we can demonstrate coarse-grained asynchrony by modifying the button’s event handler as follows:
... _button.Click += (sender, args) => { _button.IsEnabled = false; Task.Run (() => Go()); };
(We’ve chosen to use Task.Run
rather than BackgroundWorker
because the latter would do nothing to simplify our particular example.) In either case, the end result is that our entire synchronous call graph (Go
plus GetPrimesCount
) runs on a worker thread. And because Go
updates UI elements, we must now litter our code with Dispatcher.BeginInvoke
:
void Go() { for (int i = 1; i < 5; i++) { int result = GetPrimesCount (i * 1000000, 1000000); Dispatcher.BeginInvoke (new Action (() => _results.Text += result + " primes between " + (i*1000000) + " and " + ((i+1)*1000000-1) + Environment.NewLine)); } Dispatcher.BeginInvoke (new Action (() => _button.IsEnabled = true)); }
Unlike with the asynchronous version, the loop itself runs on a worker thread. This might seem innocuous, and yet, even in this simple case, our use of multithreading has introduced a race condition. (Can you spot it? If not, try running the program: it will almost certainly become apparent.)
Implementing cancellation and progress reporting creates more possibilities for thread-safety errors, as does any additional code in the method. For instance, suppose the upper limit for the loop is not hardcoded but comes from a method call:
for (int i = 1; i < GetUpperBound(); i++)
Now suppose GetUpperBound()
reads the value from a lazily loaded configuration file, which loads from disk upon first call. All of this code now runs on your worker thread, code that’s most likely not thread-safe. This is the danger of starting worker threads high in the call graph.
With any asynchronous function, you can replace the void
return type with a Task
to make the method itself usefully asynchronous (and await
able). No further changes are required:
async Task PrintAnswerToLife() // We can return Task instead of void { await Task.Delay (5000); int answer = 21 * 2; Console.WriteLine (answer); }
Notice that we don’t explicitly return a task in the method body. The compiler manufactures the task, which it signals upon completion of the method (or upon an unhandled exception). This makes it easy to create asynchronous call chains:
async Task Go() { await PrintAnswerToLife(); Console.WriteLine ("Done"); }
And because we’ve declared Go
with a Task
return type, Go
itself is awaitable.
The compiler expands asynchronous functions that return tasks into code that leverages TaskCompletionSource
to create a task that it then signals or faults.
The compiler actually calls TaskCompletionSource
indirectly, via types named Async*MethodBuilder
in the System.CompilerServices
namespace. These types handle edge cases such as putting the task into a canceled state upon an OperationCanceledException
and implementing the nuances we describe in “Asynchrony and Synchronization Contexts”.
Nuances aside, we can expand PrintAnswerToLife
into the following functional equivalent:
Task PrintAnswerToLife() { var tcs = new TaskCompletionSource<object>(); var awaiter = Task.Delay (5000).GetAwaiter(); awaiter.OnCompleted (() => { try { awaiter.GetResult(); // Rethrow any exceptions int answer = 21 * 2; Console.WriteLine (answer); tcs.SetResult (null); } catch (Exception ex) { tcs.SetException (ex); } }); return tcs.Task; }
Hence, whenever a task-returning asynchronous method finishes, execution jumps back to whoever awaited it (by virtue of a continuation).
In a rich-client scenario, execution bounces at this point back to the UI thread (if it’s not already on the UI thread). Otherwise, it continues on whatever thread the continuation came back on. This means that there’s no latency cost in bubbling up asynchronous call graphs, other than the first “bounce” if it was UI-thread-initiated.
You can return a Task<TResult>
if the method body returns TResult
:
async Task<int> GetAnswerToLife() { await Task.Delay (5000); int answer = 21 * 2; return answer; // Method has return type Task<int> we return int }
Internally, this results in the TaskCompletionSource
being signaled with a value rather than null. We can demonstrate GetAnswerToLife
by calling it from PrintAnswerToLife
(which is turn, called from Go
):
async Task Go() { await PrintAnswerToLife(); Console.WriteLine ("Done"); } async Task PrintAnswerToLife() { int answer = await GetAnswerToLife(); Console.WriteLine (answer); } async Task<int> GetAnswerToLife() { await Task.Delay (5000); int answer = 21 * 2; return answer; }
In effect, we’ve refactored our original PrintAnswerToLife
into two methods—with the same ease as if we were programming synchronously. The similarity to synchronous programming is intentional; here’s the synchronous equivalent of our call graph, for which calling Go()
gives the same result after blocking for five seconds:
void Go() { PrintAnswerToLife(); Console.WriteLine ("Done"); } void PrintAnswerToLife() { int answer = GetAnswerToLife(); Console.WriteLine (answer); } int GetAnswerToLife() { Thread.Sleep (5000); int answer = 21 * 2; return answer; }
This also illustrates the basic principle of how to design with asynchronous functions in C#:
Write your methods synchronously.
Replace synchronous method calls with asynchronous method calls, and await
them.
Except for “top-level” methods (typically event handlers for UI controls), upgrade your asynchronous methods’ return types to Task
or Task<TResult>
so that they’re awaitable.
The compiler’s ability to manufacture tasks for asynchronous functions means that for the most part, you need to explicitly instantiate a TaskCompletionSource
only in bottom-level methods that initiate I/O-bound concurrency. (And for methods that initiate compute-bound currency, you create the task with Task.Run
.)
To see exactly how this executes, it’s helpful to rearrange our code as follows:
async Task Go() { var task = PrintAnswerToLife(); await task; Console.WriteLine ("Done"); } async Task PrintAnswerToLife() { var task = GetAnswerToLife(); int answer = await task; Console.WriteLine (answer); } async Task<int> GetAnswerToLife() { var task = Task.Delay (5000); await task; int answer = 21 * 2; return answer; }
Go
calls PrintAnswerToLife
, which calls GetAnswerToLife
, which calls Delay
and then awaits. The await
causes execution to return to PrintAnswerToLife
, which itself awaits, returning to Go
, which also awaits and returns to the caller. All of this happens synchronously, on the thread that called Go
; this is the brief synchronous phase of execution.
Five seconds later, the continuation on Delay
fires and execution returns to GetAnswerToLife
on a pooled thread. (If we started on a UI thread, execution now bounces to that thread). The remaining statements in GetAnswerToLife
then run, after which the method’s Task<int>
completes with a result of 42 and executes the continuation in PrintAnswerToLife
, which executes the remaining statements in that method. The process continues until Go
’s task is signaled as complete.
Execution flow matches the synchronous call graph that we showed earlier because we’re following a pattern whereby we await
every asynchronous method right after calling it. This creates a sequential flow with no parallelism or overlapping execution within the call graph. Each await
expression creates a “gap” in execution, after which the program resumes where it left off.
Calling an asynchronous method without awaiting it allows the code that follows to execute in parallel. You might have noticed in earlier examples that we had a button whose event handler called Go
as follows:
_button.Click += (sender, args) => Go();
Despite Go
being an asynchronous method, we didn’t await it, and this is indeed what facilitates the concurrency needed to maintain a responsive UI.
We can use this same principle to run two asynchronous operations in parallel:
var task1 = PrintAnswerToLife(); var task2 = PrintAnswerToLife(); await task1; await task2;
(By awaiting both operations afterward, we “end” the parallelism at that point. Later, we’ll describe how the WhenAll
task combinator helps with this pattern.)
Concurrency created in this manner occurs whether or not the operations are initiated on a UI thread, although there’s a difference in how it occurs. In both cases, we get the same “true” concurrency occurring in the bottom-level operations that initiate it (such as Task.Delay
, or code farmed to Task.Run
). Methods above this in the call stack will be subject to true concurrency only if the operation was initiated without a synchronization context present; otherwise they will be subject to the pseudoconcurrency (and simplified thread-safety) that we talked about earlier, whereby the only places at which we can be preempted is at an await
statement. This lets us, for instance, define a shared field, _x
, and increment it in GetAnswerToLife
without locking:
async Task<int> GetAnswerToLife() { _x++; await Task.Delay (5000); return 21 * 2; }
(We would, though, be unable to assume that _x
had the same value before and after the await
.)
Just as ordinary named methods can be asynchronous:
async Task NamedMethod() { await Task.Delay (1000); Console.WriteLine ("Foo"); }
so can unnamed methods (lambda expressions and anonymous methods), if preceded by the async
keyword:
Func<Task> unnamed = async () => { await Task.Delay (1000); Console.WriteLine ("Foo"); };
We can call and await these in the same way:
await NamedMethod(); await unnamed();
Asynchronous lambda expressions can be used when attaching event handlers:
myButton.Click += async (sender, args) => { await Task.Delay (1000); myButton.Content = "Done"; };
This is more succinct than the following, which has the same effect:
myButton.Click += ButtonHandler; ... async void ButtonHander (object sender, EventArgs args) { await Task.Delay (1000); myButton.Content = "Done"; };
Asynchronous lambda expressions can also return Task<TResult>
:
Func<Task<int>> unnamed = async () => { await Task.Delay (1000); return 123; }; int answer = await unnamed();
In WinRT, the equivalent of Task
is IAsyncAction
and the equivalent of Task<TResult>
is IAsyncOperation<TResult>
(defined in the Windows.Foundation
namespace).
You can convert from either into a Task
or Task<TResult>
via the AsTask
extension method in the System.Runtime.WindowsRuntime.dll
assembly. This assembly also defines a GetAwaiter
method that operates on IAsyncAction
and IAsyncOperation<TResult>
types, which allows you to await them directly. For instance:
Task<StorageFile> fileTask = KnownFolders.DocumentsLibrary.CreateFileAsync ("test.txt").AsTask();
or:
StorageFile file = await KnownFolders.DocumentsLibrary.CreateFileAsync ("test.txt");
Due to limitations in the COM type system, IAsyncOperation<TResult>
is not based on IAsyncAction
as you might expect. Instead, both inherit from a common base type called IAsyncInfo
.
The AsTask
method is also overloaded to accept a cancellation token (see “Cancellation”) and an IProgress<T>
object (see “Progress Reporting”).
We’ve already seen how the presence of a synchronization context is significant in terms of posting continuations. There are a couple of other more subtle ways in which such synchronization contexts come into play with void-returning asynchronous functions. These are not a direct result of C# compiler expansions, but a function of the Async*MethodBuilder
types in the System.CompilerServices
namespace that the compiler uses in expanding asynchronous functions.
It’s common practice in rich-client applications to rely on the central exception-handling event (Application.DispatcherUnhandledException
in WPF) to process unhandled exceptions thrown on the UI thread. And in ASP.NET applications, the Application_Error
in global.asax does a similar job. Internally, they work by invoking UI events (or in ASP.NET, the pipeline of page processing methods) in their own try
/catch
block.
Top-level asynchronous functions complicate this. Consider the following event handler for a button click:
async void ButtonClick (object sender, RoutedEventArgs args) { await Task.Delay(1000); throw new Exception ("Will this be ignored?"); }
When the button is clicked and the event handler runs, execution returns normally to the message loop after the await
statement, and the exception that’s thrown a second later cannot be caught by the catch
block in the message loop.
To mitigate this problem, AsyncVoidMethodBuilder
catches unhandled exceptions (in void-returning asynchronous functions) and posts them to the synchronization context if present, ensuring that global exception-handling events still fire.
The compiler applies this logic only to void-returning asynchronous functions. So if we changed ButtonClick
to return a Task
instead of void
, the unhandled exception would fault the resultant Task
, which would then have nowhere to go (resulting in an unobserved exception).
An interesting nuance is that it makes no difference whether you throw before or after an await
. So in the following example, the exception is posted to the synchronization context (if present) and never to the caller:
async void Foo() { throw null; await Task.Delay(1000); }
If no synchronization context is present, the exception will go unobserved. It might seem odd that the exception isn’t thrown right back to the caller, although it’s not entirely different to what happens with iterators:
IEnumerable<int> Foo() { throw null; yield return 123; }
In this example, an exception is never thrown straight back to the caller: not until the sequence is enumerated is the exception thrown.
If a synchronization context is present, void-returning asynchronous functions also call its OperationStarted
method upon entering the function and its OperationCompleted
method when the function finishes. These methods are leveraged by ASP.NET’s synchronization context to ensure sequential execution in the page-processing pipeline.
Overriding these methods is useful if writing a custom synchronization context for unit testing void-returning asynchronous methods. This is discussed on Microsoft’s Parallel Programming blog at http://blogs.msdn.com/b/pfxteam.
An asynchronous function may return before awaiting. Consider the following method that caches the downloading of web pages:
static Dictionary<string,string> _cache = new Dictionary<string,string>(); async Task<string> GetWebPageAsync (string uri) { string html; if (_cache.TryGetValue (uri, out html)) return html; return _cache [uri] = await new WebClient().DownloadStringTaskAsync (uri); }
Should a URI already exist in the cache, execution returns to the caller with no awaiting having occurred, and the method returns an already-signaled task. This is referred to as synchronous completion.
When you await a synchronously completed task, execution does not return to the caller and bounce back via a continuation—instead, it proceeds immediately to the next statement. The compiler implements this optimization by checking the IsCompleted
property on the awaiter; in other words, whenever you await:
Console.WriteLine (await GetWebPageAsync ("http://oreilly.com"));
the compiler emits code to short-circuit the continuation in case of synchronization completion:
var awaiter = GetWebPageAsync().GetAwaiter(); if (awaiter.IsCompleted) Console.WriteLine (awaiter.GetResult()); else awaiter.OnCompleted (() => Console.WriteLine (awaiter.GetResult());
Awaiting an asynchronous function that returns synchronously still incurs a small overhead—maybe 50-100 nanoseconds on a 2015-era PC.
In contrast, bouncing to the thread pool introduces the cost of a context switch—perhaps one or two microseconds, and bouncing to a UI message loop, at least 10 times that (much longer if the UI thread is busy).
It’s even legal to write asynchronous methods that never await, although the compiler will generate a warning:
async Task<string> Foo() { return "abc"; }
Such methods can be useful when overriding virtual/abstract methods if your implementation doesn’t happen to need asynchrony. (An example is MemoryStream
’s ReadAsync
/WriteAsync
methods—see Chapter 15.) Another way to achieve the same result is to use Task.FromResult
, which returns an already-signaled task:
Task<string> Foo() { return Task.FromResult ("abc"); }
Our GetWebPageAsync
method is implicitly thread-safe if called from a UI thread, in that you could invoke it several times in succession (thereby initiating multiple concurrent downloads), and no locking is required to protect the cache. If the series of calls were to the same URI, though, we’d end up initiating multiple redundant downloads, all of which would eventually update the same cache entry (the last one winning). While not erroneous, it would be more efficient if subsequent calls to the same URI could instead (asynchronously) wait upon the result of the in-progress request.
There’s an easy way to accomplish this—without resorting to locks or signaling constructs. Instead of a cache of strings, we create a cache of “futures” (Task<string>
):
static Dictionary<string,Task<string>> _cache = new Dictionary<string,Task<string>>(); Task<string> GetWebPageAsync (string uri) { Task<string> downloadTask; if (_cache.TryGetValue (uri, out downloadTask)) return downloadTask; return _cache [uri] = new WebClient().DownloadStringTaskAsync (uri); }
(Notice that we don’t mark the method as async
, because we’re directly returning the task we obtain from calling WebClient
’s method).
If we call GetWebPageAsync
repeatedly with the same URI, we’re now guaranteed to get the same Task<string>
object back. (This has the additional benefit of minimizing GC load.) And if the task is complete, awaiting it is cheap, thanks to the compiler optimization that we just discussed.
We could further extend our example to make it thread-safe without the protection of a synchronization context, by locking around the entire method body:
lock (_cache) { Task<string> downloadTask; if (_cache.TryGetValue (uri, out downloadTask)) return downloadTask; return _cache [uri] = new WebClient().DownloadStringTaskAsync (uri); }
This works because we’re not locking for the duration of downloading a page (which would hurt concurrency); we’re locking for the small duration of checking the cache, starting a new task if necessary, and updating the cache with that task.
For methods that are called many times in a loop, you can avoid the cost of repeatedly bouncing to a UI message loop by calling ConfigureAwait
. This forces a task not to bounce continuations to the synchronization context, cutting the overhead closer to the cost of a context switch (or much less if the method that you’re awaiting completes synchronously):
async void A() { ... await B(); ... } async Task B() { for (int i = 0; i < 1000; i++) await C().ConfigureAwait (false); } async Task C() { ... }
This means that for the B
and C
methods, we rescind the simple thread-safety model in UI apps whereby code runs on the UI thread and can be preempted only during an await
statement. Method A
, however, is unaffected and will remain on a UI thread if it started on one.
This optimization is particularly relevant when writing libraries: you don’t need the benefit of simplified thread-safety because your code typically does not share state with the caller—and does not access UI controls. (It would also make sense, in our example, for method C to complete synchronously if it knew the operation was likely to be short-running.)
It’s often important to be able to cancel a concurrent operation after it’s started, perhaps in response to a user request. A simple way to implement this is with a cancellation flag, which we could encapsulate by writing a class like this:
class CancellationToken { public bool IsCancellationRequested { get; private set; } public void Cancel() { IsCancellationRequested = true; } public void ThrowIfCancellationRequested() { if (IsCancellationRequested) throw new OperationCanceledException(); } }
We could then write a cancellable asynchronous method as follows:
async Task Foo (CancellationToken cancellationToken) { for (int i = 0; i < 10; i++) { Console.WriteLine (i); await Task.Delay (1000); cancellationToken.ThrowIfCancellationRequested(); } }
When the caller wants to cancel, it calls Cancel
on the cancellation token that it passed into Foo
. This sets IsCancellationRequested
to true which causes Foo
to fault a short time later with an OperationCanceledException
(a predefined exception in the System
namespace designed for this purpose).
Thread-safety aside (we should be locking around reading/writing IsCancellationRequested
), this pattern is effective and the CLR provides a type called CancellationToken
which is very similar to what we’ve just shown. However, it lacks a Cancel
method; this method is instead exposed on another type called CancellationTokenSource
. This separation provides some security: a method which has access only to a CancellationToken
object can check for but not initiate cancellation.
To get a cancellation token, we first instantiate a CancellationTokenSource
:
var cancelSource = new CancellationTokenSource();
This exposes a Token
property which returns a CancellationToken
. Hence, we could call our Foo
method as follows:
var cancelSource = new CancellationTokenSource(); Task foo = Foo (cancelSource.Token); ... ... (some time later) cancelSource.Cancel();
Most asynchronous methods in the CLR support cancellation tokens, including Delay
. If we modify Foo
such that it passes its token into the Delay
method, the task will end immediately upon request (rather than up to a second later):
async Task Foo (CancellationToken cancellationToken) { for (int i = 0; i < 10; i++) { Console.WriteLine (i); await Task.Delay (1000, cancellationToken); } }
Notice that we no longer need to call ThrowIfCancellationRequested
because Task.Delay
is doing that for us. Cancellation tokens propagate nicely down the call stack (just as cancellation requests cascade up the call stack, by virtue of being exceptions).
Asynchronous methods in WinRT follow an inferior protocol for cancellation whereby instead of accepting a CancellationToken
, the IAsyncInfo
type exposes a Cancel
method. The AsTask
extension method is overloaded to accept a cancellation token, however, bridging the gap.
Synchronous methods can support cancellation, too (such as Task
’s Wait
method). In such cases, the instruction to cancel will have to come asynchronously (e.g., from another task). For example:
var cancelSource = new CancellationTokenSource(); Task.Delay (5000).ContinueWith (ant => cancelSource.Cancel()); ...
In fact, from Framework 4.5, you can specify a time interval when constructing CancellationTokenSource
to initiate cancellation after a set period of time (just as we demonstrated). It’s useful for implementing timeouts, whether synchronous or asynchronous:
var cancelSource = new CancellationTokenSource (5000); try { await Foo (cancelSource.Token); } catch (OperationCanceledException ex) { Console.WriteLine ("Cancelled"); }
The CancellationToken
struct provides a Register
method which lets you register a callback delegate that will be fired upon cancellation; it returns an object that can be disposed to undo the registration.
Tasks generated by the compiler’s asynchronous functions automatically enter a “Canceled” state upon an unhandled OperationCanceledException
(IsCanceled
returns true
and IsFaulted
returns false
). The same goes for tasks created with Task.Run
for which you pass the (same) CancellationToken
to the constructor. The distinction between a faulted and a canceled task is unimportant in asynchronous scenarios, in that both throw an OperationCanceledException
when awaited; it matters in advanced parallel programming scenarios (specifically conditional continuations). We pick up this topic in “Canceling Tasks” in Chapter 23.
Sometimes you’ll want an asynchronous operation to report back progress as it’s running. A simple solution is to pass an Action
delegate to the asynchronous method, which the method fires whenever progress changes:
Task Foo (Action<int> onProgressPercentChanged) { return Task.Run (() => { for (int i = 0; i < 1000; i++) { if (i % 10 == 0) onProgressPercentChanged (i / 10); // Do something compute-bound... } }); }
Here’s how we could call it:
Action<int> progress = i => Console.WriteLine (i + " %"); await Foo (progress);
While this works well in a Console application, it’s not ideal in rich-client scenarios because it reports progress from a worker thread, causing potential thread-safety issues for the consumer. (In effect, we’ve allowed a side-effect of concurrency to “leak” to the outside world, which is unfortunate as the method is otherwise isolated if called from a UI thread.)
The CLR provides a pair of types to solve this problem: an interface called IProgress<T>
and a class that implements this interface called Progress<T>
. Their purpose, in effect, is to “wrap” a delegate, so that UI applications can report progress safely through the synchronization context.
The interface defines just one method:
public interface IProgress<in T> { void Report (T value); }
Using IProgress<T>
is easy: our method hardly changes:
Task Foo (IProgress<int> onProgressPercentChanged) { return Task.Run (() => { for (int i = 0; i < 1000; i++) { if (i % 10 == 0) onProgressPercentChanged.Report (i / 10); // Do something compute-bound... } }); }
The Progress<T>
class has a constructor that accepts a delegate of type Action<T>
that it wraps:
var progress = new Progress<int> (i => Console.WriteLine (i + " %")); await Foo (progress);
(Progress<T>
also has a ProgressChanged
event that you can subscribe to instead of [or in addition to] passing an action delegate to the constructor.) Upon instantiating Progress<int>
, the class captures the synchronization context, if present. When Foo
then calls Report
, the delegate is invoked through that context.
Asynchronous methods can implement more elaborate progress reporting by replacing int
with a custom type that exposes a range of properties.
If you’re familiar with Reactive Framework, you’ll notice that IProgress<T>
together with the task returned by the asynchronous function provide a feature set similar to IObserver<T>
. The difference is that a task can expose a “final” return value in addition to (and differently typed to) the values emitted by IProgress<T>
.
Values emitted by IProgress<T>
are typically “throwaway” values (e.g., percent complete or bytes downloaded so far) whereas values pushed by IObserver<T>
’s OnNext
typically comprise the result itself and are the very reason for calling it.
Asynchronous methods in WinRT also offer progress reporting, although the protocol is complicated by COM’s (relatively) retarded type system. Instead of accepting an IProgress<T>
object, asynchronous WinRT methods that report progress return one of the following interfaces, in place of IAsyncAction
and IAsyncOperation<TResult>
:
IAsyncActionWithProgress<TProgress> IAsyncOperationWithProgress<TResult, TProgress>
Interestingly, both and are based on IAsyncInfo
(and not IAsyncAction
and IAsyncOperation<TResult>
).
The good news is that the AsTask
extension method is also overloaded to accept IProgress<T>
for the above interfaces, so as a .NET consumer, you can ignore the COM interfaces and do this:
var progress = new Progress<int> (i => Console.WriteLine (i + " %")); CancellationToken cancelToken = ... var task = someWinRTobject.FooAsync().AsTask (cancelToken, progress);
Framework 4.5 and later exposes hundreds of task-returning asynchronous methods that you can await
(mainly related to I/O). Most of these methods (at least partly) follow a pattern called the Task-based Asynchronous Pattern (TAP) which is a sensible formalization of what we have described to date. A TAP method:
Returns a “hot” (running) Task
or Task<TResult>
Has a “Async” suffix (except for special cases such as task combinators)
Is overloaded to accept a cancellation token and/or IProgress<T>
if it supports cancellation and/or progress reporting
Returns quickly to the caller (has only a small initial synchronous phase)
Does not tie up a thread if I/O-bound
As we’ve seen, TAP methods are easy to write with C#’s asynchronous functions.
A nice consequence of there being a consistent protocol for asynchronous functions (whereby they consistently return tasks) is that it’s possible to use and write task combinators—functions that usefully combine tasks, without regard for what those specific tasks do.
The CLR includes two task combinators: Task.WhenAny
and Task.WhenAll
. In describing them, we’ll assume the following methods are defined:
async Task<int> Delay1() { await Task.Delay (1000); return 1; } async Task<int> Delay2() { await Task.Delay (2000); return 2; } async Task<int> Delay3() { await Task.Delay (3000); return 3; }
Task.WhenAny
returns a task that completes when any one of a set of tasks complete. The following completes in one second:
Task<int> winningTask = await Task.WhenAny (Delay1(), Delay2(), Delay3()); Console.WriteLine ("Done"); Console.WriteLine (winningTask.Result); // 1
Because Task.WhenAny
itself returns a task, we await it, which returns the task that finished first. Our example is entirely nonblocking—including the last line when we access the Result
property (because winningTask
will already have finished). Nonetheless, it’s usually better to await
the winningTask
:
Console.WriteLine (await winningTask); // 1
because any exceptions are then rethrown without an AggregateException
wrapping. In fact, we can perform both await
s in one step:
int answer = await await Task.WhenAny (Delay1(), Delay2(), Delay3());
If a non-winning task subsequently faults, the exception will go unobserved unless you subsequently await the task (or query its Exception
property).
WhenAny
is useful for applying timeouts or cancellation to operations that don’t otherwise support it:
Task<string> task = SomeAsyncFunc(); Task winner = await (Task.WhenAny (task, Task.Delay(5000))); if (winner != task) throw new TimeoutException(); string result = await task; // Unwrap result/rethrow
Notice that because in this case we’re calling WhenAny
with differently typed tasks, the winner is reported as a plain Task
(rather than a Task<string>
).
Task.WhenAll
returns a task that completes when all of the tasks that you pass to it complete. The following completes after three seconds (and demonstrates the fork/join pattern):
await Task.WhenAll (Delay1(), Delay2(), Delay3());
We could get a similar result by awaiting task1
, task2
and task3
in turn rather than using WhenAll
:
Task task1 = Delay1(), task2 = Delay2(), task3 = Delay3(); await task1; await task2; await task3;
The difference (apart from it being less efficient by virtue of requiring three awaits rather than one), is that should task1
fault, we’ll never get to await task2
/task3
, and any of their exceptions will go unobserved. In fact, this is why they relaxed the unobserved task exception behavior from CLR 4.5: it would be confusing if, despite an exception handling block around the entire code block above, an exception from task2
or task3
could crash your application sometime later when garbage collected.
In contrast, Task.WhenAll
doesn’t complete until all tasks have completed—even when there’s a fault. And if there are multiple faults, their exceptions are combined into the task’s AggregateException
(this is when AggregateException
actually becomes useful—should you be interested in all the exceptions, that is). Awaiting the combined task, however, throws only the first exception, so to see all the exceptions you need to do this:
Task task1 = Task.Run (() => { throw null; } ); Task task2 = Task.Run (() => { throw null; } ); Task all = Task.WhenAll (task1, task2); try { await all; } catch { Console.WriteLine (all.Exception.InnerExceptions.Count); // 2 }
Calling WhenAll
with tasks of type Task<TResult>
returns a Task<TResult[]>
, giving the combined results of all the tasks. This reduces to a TResult[]
when awaited:
Task<int> task1 = Task.Run (() => 1); Task<int> task2 = Task.Run (() => 2); int[] results = await Task.WhenAll (task1, task2); // { 1, 2 }
To give a practical example, the following downloads URIs in parallel and sums their total length:
async Task<int> GetTotalSize (string[] uris) { IEnumerable<Task<byte[]>> downloadTasks = uris.Select (uri => new WebClient().DownloadDataTaskAsync (uri)); byte[][] contents = await Task.WhenAll (downloadTasks); return contents.Sum (c => c.Length); }
There’s a slight inefficiency here, though, in that we’re unnecessarily hanging onto the byte arrays that we download until every task is complete. It would be more efficient if we collapsed byte arrays into their lengths right after downloading them. This is where an asynchronous lambda comes in handy, because we need to feed an await
expression into LINQ’s Select
query operator:
async Task<int> GetTotalSize (string[] uris) { IEnumerable<Task<int>> downloadTasks = uris.Select (async uri => (await new WebClient().DownloadDataTaskAsync (uri)).Length); int[] contentLengths = await Task.WhenAll (downloadTasks); return contentLengths.Sum(); }
It can be useful to write your own task combinators. The simplest “combinator” accepts a single task, such as the following, which lets you await any task with a timeout:
async static Task<TResult> WithTimeout<TResult> (this Task<TResult> task, TimeSpan timeout) { Task winner = await (Task.WhenAny (task, Task.Delay (timeout))); if (winner != task) throw new TimeoutException(); return await task; // Unwrap result/rethrow }
The following lets you “abandon” a task via a CancellationToken
:
static Task<TResult> WithCancellation<TResult> (this Task<TResult> task, CancellationToken cancelToken) { var tcs = new TaskCompletionSource<TResult>(); var reg = cancelToken.Register (() => tcs.TrySetCanceled ()); task.ContinueWith (ant => { reg.Dispose(); if (ant.IsCanceled) tcs.TrySetCanceled(); else if (ant.IsFaulted) tcs.TrySetException (ant.Exception.InnerException); else tcs.TrySetResult (ant.Result); }); return tcs.Task; }
Task combinators can be complex to write, sometimes requiring the use of signaling constructs that we cover in Chapter 22. This is actually a good thing, because it keeps concurrency-related complexity out of your business logic and into reusable methods that can be tested in isolation.
The next combinator works like WhenAll
, except that if any of the tasks fault, the resultant task faults immediately:
async Task<TResult[]> WhenAllOrError<TResult> (params Task<TResult>[] tasks) { var killJoy = new TaskCompletionSource<TResult[]>(); foreach (var task in tasks) task.ContinueWith (ant => { if (ant.IsCanceled) killJoy.TrySetCanceled(); else if (ant.IsFaulted) killJoy.TrySetException (ant.Exception.InnerException); }); return await await Task.WhenAny (killJoy.Task, Task.WhenAll (tasks)); }
We start by creating a TaskCompletionSource
whose sole job is to end the party if a task faults. Hence, we never call its SetResult
method; only its TrySetCanceled
and TrySetException
methods. In this case, ContinueWith
is more convenient than GetAwaiter().OnCompleted
because we’re not accessing the tasks’ results and wouldn’t want to bounce to a UI thread at that point.
The Framework employs other patterns for asynchrony which precede tasks and asynchronous functions. These are now rarely required, since task-based asynchrony has become the dominant pattern as of Framework 4.5.
The oldest pattern is called the APM (“Asynchronous Programming Model”) and uses a pair of methods starting in “Begin” and “End,” and an interface called IAsyncResult
. To illustrate, we’ll take the Stream
class in System.IO
, and look at its Read
method. First, the synchronous version:
public int Read (byte[] buffer, int offset, int size);
You can probably predict what the task-based asynchronous version looks like:
public Task<int> ReadAsync (byte[] buffer, int offset, int size);
Now let’s examine the APM version:
public IAsyncResult BeginRead (byte[] buffer, int offset, int size, AsyncCallback callback, object state); public int EndRead (IAsyncResult asyncResult);
Calling the Begin*
method initiates the operation, returning an IAsyncResult
object which acts as a token for the asynchronous operation. When the operation completes (or faults), the AsyncCallback
delegate fires:
public delegate void AsyncCallback (IAsyncResult ar);
Whoever handles this delegate then calls the End*
method which provides the operation’s return value, as well as rethrowing an exception if the operation faulted.
The APM is not only awkward to use, but surprisingly difficult to implement correctly. The easiest way to deal with APM methods is to call the Task.Factory.FromAsync
adapter method, which converts an APM method pair into a Task
. Internally, it uses a TaskCompletionSource
to give you a task that’s signaled when an APM operation completes or faults.
The FromAsync
method requires the following parameters:
A delegate specifying a BeginXXX
method
A delegate specifying a EndXXX
method
Additional arguments that will get passed to these methods
FromAsync
is overloaded to accept delegate types and arguments that match nearly all the asynchronous method signatures found in the .NET Framework. For instance, assuming stream
is a Stream
and buffer
is a byte[]
, we could do this:
Task<int> readChunk = Task<int>.Factory.FromAsync ( stream.BeginRead, stream.EndRead, buffer, 0, 1000, null);
The CLR still supports asynchronous delegates, a feature whereby you can call any delegate asynchronously using APM-style BeginInvoke
/EndInvoke
methods:
Func<string> foo = () => { Thread.Sleep(1000); return "foo"; }; foo.BeginInvoke (asyncResult => Console.WriteLine (foo.EndInvoke (asyncResult)), null);
Asynchronous delegates incur a surprising overhead—and are painfully redundant with tasks:
Func<string> foo = () => { Thread.Sleep(1000); return "foo"; }; Task.Run (foo).ContinueWith (ant => Console.WriteLine (ant.Result));
The Event-based Asynchronous Pattern (EAP) was introduced in Framework 2.0 to provide a simpler alternative to the APM, particularly in UI scenarios. It was implemented in only a handful of types, however, most notably WebClient
in System.Net
. The EAP is just a pattern; no types are provided to assist. Essentially the pattern is this: a class offers a family of members that internally manage concurrency, similar to the following.
// These members are from the WebClient class: public byte[] DownloadData (Uri address); // Synchronous version public void DownloadDataAsync (Uri address); public void DownloadDataAsync (Uri address, object userToken); public event DownloadDataCompletedEventHandler DownloadDataCompleted; public void CancelAsync (object userState); // Cancels an operation public bool IsBusy { get; } // Indicates if still running
The *Async
methods initiate an operation asynchronously. When the operation completes, the *Completed
event fires (automatically posting to the captured synchronization context if present). This event passes back an event arguments object that contains:
A flag indicating whether the operation was canceled (by the consumer calling CancelAsync
)
An Error
object indicating an exception that was thrown (if any)
The userToken
object if supplied when calling the Async
method
EAP types may also expose a progress reporting event, which fires whenever progress changes (also posted through the synchronization context):
public event DownloadProgressChangedEventHandler DownloadProgressChanged;
Implementing the EAP requires a large amount of boilerplate code, making the pattern poorly compositional.
BackgroundWorker
in System.ComponentModel
is a general-purpose implementation of the EAP. It allows rich-client apps to start a worker thread and report completion and percentage-based progress without needing to explicitly capture synchronization context. For instance:
var worker = new BackgroundWorker { WorkerSupportsCancellation = true }; worker.DoWork += (sender, args) => { // This runs on a worker thread if (args.Cancel) return; Thread.Sleep(1000); args.Result = 123; }; worker.RunWorkerCompleted += (sender, args) => { // Runs on UI thread // We can safely update UI controls here... if (args.Cancelled) Console.WriteLine ("Cancelled"); else if (args.Error != null) Console.WriteLine ("Error: " + args.Error.Message); else Console.WriteLine ("Result is: " + args.Result); }; worker.RunWorkerAsync(); // Captures sync context and starts operation
RunWorkerAsync
starts the operation, firing the DoWork
event on a pooled worker thread. It also captures the synchronization context, and when the operation completes (or faults), the RunWorkerCompleted
event is invoked through that synchronization context (like a continuation).
BackgroundWorker
creates coarse-grained concurrency, in that the DoWork
event runs entirely on a worker thread. If you need to update UI controls in that event handler (other than posting a percentage-complete message), you must use Dispatcher.BeginInvoke
or similar).
We describe BackgroundWorker
in more detail at http://albahari.com/threading.
1 The CLR creates other threads behind the scenes for garbage collection and finalization.