Asynchronous programming is essentially having routines that returns immediately after being called. I was first exposed to this kind of calls with Ajax on the browser.
C# 5 introduced the
await keywords which are very useful for making calls to asynchronous methods without breaking the linear flow of the program.
I had a hard time understanding
async/await and especially the difference between the
Task.Wait method and the
In this post, We will try to explain the very basic principle of
Methods returning Tasks
The TPL library allows us to create a
Task object which represents a on going operation, the operation is usually executed in a different thread (but not always depending on the synchronization context).
Consider the following:
Creating a method that returns a task is basically creating an asynchronous method that will return its
Task object immediately when it is called, as in the following example:
Whenever called the
GetIntAsync method returns immediately, to get the result returned by the task we have to wait for it somehow.
Task class provides methods that allows us to wait synchronously for the operation completion, calling
Task.Wait or referencing the
Task.Result property will cause the current thread to block waiting for the completion of the task, consider the following:
Before proceeding to doing stuff with
DoStuff method have to wait synchronously for the task completion.
This basically defeats the purpose of asynchrony which we use to get a chance to do something else on the same thread while an operation completes.
This is especially useful for IO bound operation that involves using high latency media such as networks and disks. Instead of waiting for IO in the current thread we can do useful work instead and wait for IO in another thread.
Fortunately, we don’t have to wait synchronously each time we need the result of an asynchronous operation, we can schedule a callback that will be executed on task completion.
Scheduling a continuation (aka callback)
Task class provides the
ContinueWith method to which we can pass a statement lambda that will be executed on task completion, consider the following:
DoStuff method does not block waiting for
GetIntAsync to complete, instead it schedules a continuation to be executed when the task completes. Notice how the statement lambda gets
res as an argument.
This style of passing callback can get feisty in complex scenarii and we can end up with a callback hell in which we have to jump from callback to callback to follow the execution logic.
await keyword allows to avoid this callback hell by basically “waiting without blocking” without breaking the linear flow of the program.
The Await keyword schedules continuations for us
Consider the example:
await keyword is here used to call the asynchronous
GetIntAsync method, notice here how we assigned the returned result directly to an
int variable that can be used by the subsequent statements.
Note here that
await is not blocking the current thread and the do something with res part is only executed when
GetIntAsync is finished.
await keyword basically instructs the C# compiler to:
- take all statements after await
- package them into a lambda
- generate code that schedules the lambda for us
Instead of registering callbacks our selves, the
await keywords allows us to delegate the work to the compiler.
This allows us to have a linear code flow while leveraging the benefits of asynchrony i.e. waiting without blocking.
Note also how we marked
DoStuff as an
async method returning a
Task, all methods using the
await keyword must be marked as
async, since they are waiting without blocking they become asynchronous methods themselves and returns immediately when called.
We don’t have to deal with the generated code and of course in reality the process is more complex than described earlier, checkout the closing thoughts for pointers to details.
What happens when an exception is thrown?
The great thing about
async/await is that most of the rest language feature work as expected, exception for example:
The thrown exception can be caught and the
catch block will be executed when the exception is thrown in the asynchronous operation, so no worries.
Also check out chapter 28 of CLR via C#, 4th Edition by Jeffrey Richter for a discussion about the benefits of asynchrony for IO bound operations.