C# 5.0 introduces the await
and async
keywords to support asynchronous
programming, a style of programming where long-running
functions do most or all of their work after
returning to the caller. This is in contrast to normal synchronous programming, where
long-running functions block the caller until the
operation is complete. Asynchronous programming implies concurrency, since the long-running
operation continues in parallel to the caller. The
implementer of an asynchronous
function initiates this concurrency either through multithreading (for compute-bound operations) or via a
callback mechanism (for I/O-bound operations).
Multithreading, concurrency, and asynchronous programming are large topics. We dedicate two chapters to them in C# 5.0 in a Nutshell, and discuss them online at http://albahari.com/threading.
For instance, consider the following synchronous method, which is long-running and compute-bound:
int ComplexCalculation() { double x = 2; for (int i = 1; i < 100000000; i++) x += Math.Sqrt (x) / i; return (int)x; }
This method blocks the caller for a few seconds while it runs. The result of the calculation is then returned to the caller:
int result
= ComplexCalculation();
// Sometime later:
Console.WriteLine (result); // 116
The CLR defines a class called Task<TResult>
(in System.Threading.Tasks
) to encapsulate the
concept of an operation that completes in the future. You can generate a
Task<TResult>
for a compute-bound
operation by calling Task.Run
, which
tells the CLR to run the specified delegate on a separate thread that
executes in parallel to the caller:
Task<int>
ComplexCalculationAsync
() { returnTask.Run (
() => ComplexCalculation())
; }
This method is asynchronous because it returns
immediately to the caller while it executes concurrently. However, we need
some mechanism to allow the caller to specify what should happen when the
operation finishes and the result becomes available. Task<TResult>
solves this by exposing a
GetAwaiter
method which lets the caller
attach a continuation:
Task<int> task = ComplexCalculationAsync(); var awaiter = task.GetAwaiter
(); awaiter.OnCompleted
(() => // Continuation { int result = awaiter.GetResult
(); Console.WriteLine (result); // 116 });
This says to the operation, “When you finish, execute the specified
delegate.” Our continuation first calls GetResult
which returns the result of the
calculation. (Or, if the task faulted [threw an
exception], calling GetResult
rethrows
that exception.) Our continuation then writes out the result via Console.WriteLine
.
The await
keyword
simplifies the attaching of continuations. Starting with a basic
scenario, the compiler expands:
varresult
=await
expression
;statement(s)
;
into something functionally similar to:
var awaiter =expression
.GetAwaiter(); awaiter.OnCompleted (() => { varresult
= awaiter.GetResult();statement(s)
; );
The compiler also emits code to optimize the scenario of the operation completing synchronously (immediately). The most common reason for an asynchronous operation completing immediately is if it implements an internal caching mechanism, and the result is already cached.
Hence, we can call the ComplexCalculationAsync
method we defined
previously, like this:
int result = await
ComplexCalculationAsync();
Console.WriteLine (result);
In order to compile, we need to add the async
modifier to the containing
method:
async
void Test() { int result =await
ComplexCalculationAsync(); 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.0 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.
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 (by
virtue of calling GetResult
);
otherwise, its return value is assigned to the await
expression.
The CLR’s implementation of a task awaiter’s OnCompleted
method ensures that by default,
continuations are posted through the current synchronization
context, if one is present. In practice, this means that in
rich-client UI scenarios (WPF, Metro, Silverlight, and Windows Forms),
if you await
on a UI thread, your
code will continue on that same
thread. This simplifies thread safety.
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
which tests for synchronous completion)—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!");
Task.Delay
is a static method
that returns a Task
that completes in
the specified number of milliseconds. The
synchronous equivalent of Task.Delay
is Thread.Sleep
.
Task
is the nongeneric base
class of Task<TResult>
and is
functionally equivalent to
Task<TResult>
except that it
has no result.
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 catch
or finally
block, a lock
expression, an unsafe
context, or an executable’s entry point
(main method).
In the following example, we await
inside a loop:
async void Test()
{
for (int i = 0; i < 10; i++)
{
int result = await
ComplexCalculationAsync();
Console.WriteLine (result);
}
}
Upon first executing ComplexCalculationAsync
, 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. The compiler achieves
this by translating such code into a state machine, like it does with
iterators.
Without the await
keyword, the
manual use of continuations means that you must write something
equivalent to a state machine. This is traditionally what makes
asynchronous programming difficult.
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()
{
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 Go
returns a
Task
, Go
itself is awaitable.) The compiler expands
asynchronous functions that return tasks into code that (indirectly)
leverages TaskCompletionSource
to
create a task that it then signals or faults.
TaskCompletionSource
is a CLR
type that lets you create tasks that you manually control, signaling
them as complete with a result (or as faulted with an exception).
Unlike Task.Run
, TaskCompletionSource
doesn’t tie up a thread
for the duration of the operation. It’s also used for writing
I/O-bound task-returning methods (such as Task.Delay
).
The aim is to ensure that when a task-returning asynchronous method finishes, execution can jump back to whoever awaited it, via a continuation.
You can return a Task<TResult>
if the method body
returns TResult
:
asyncTask<int>
GetAnswerToLife() { await Task.Delay (5000); int answer = 21 * 2; // answer isint
so our method returns Task<int
> returnanswer
; }
We can demonstrate GetAnswerToLife
by calling it from PrintAnswerToLife
(which is, in
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; }
Asynchronous functions make asynchronous programming similar to
synchronous programming. 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#, which is to write your methods
synchronously, and then replace synchronous
method calls with asynchronous method calls, and
await
them.
We’ve just demonstrated the most common pattern, which is to
await
task-returning functions right
after calling them. This results in sequential program flow that’s
logically similar to the synchronous equivalent.
Calling an asynchronous method without awaiting it allows the code
that follows to execute in parallel. For example, the following executes
PrintAnswerToLife
twice,
concurrently:
var task1 = PrintAnswerToLife(); var task2 = PrintAnswerToLife(); await task1; await task2;
By awaiting both operations afterward, we “end” the parallelism at that point (and rethrow any
exceptions from those tasks). The Task
class provides a static method called
WhenAll
to achieve the same result
slightly more efficiently. WhenAll
returns a task that completes when all of the tasks that you pass to it
complete:
await Task.WhenAll (PrintAnswerToLife(), PrintAnswerToLife());
WhenAll
is a called
task combinator. (The Task
class also provides a task combinator
called WhenAny
, which completes when
any of the tasks provided to it complete.) We cover
the task combinators in detail in C# 5.0 in a
Nutshell.
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();