By far my favorite feature of .NET 4.5 is async
/await
, or, as Microsoft call it, the Task-based Asynchronous Pattern (TAP). It is something I didn’t know I was missing until I saw a recording of Anders Hejlsberg’s Build talk on the subject. Shortly after this, I had a highly asynchronous C++ embedded project that lasted for over a year where I felt miserable building state machine after state machine to handle the inherent problem of all asynchronous programs: What do you do after your asynchronous operation completes?
This blog series is aimed at C# programmers new to async
/await
. A general understanding of the Task
class introduced with .NET 4.0 is expected. In this first part, I’ll explain the async
in async
/await
.
What is async?
The async
keyword can be used to decorate a method or lambda expression.
It is important to note that async
is not a part of the method signature, so if you are implementing an interface or overriding a virtual or abstract method you have a choice of whether to use async
or not.
Return types
An async
method may only return void
, Task
or Task<T>
for any concrete type T.
void
as a return type should be avoided where possible and is generally only needed in event handlers, making the method a fire-and-forget method where the caller has no way of knowing when the call finished or failed.
If you write a method returning Task
or Task<T>
you should generally follow the convention of using the suffix Async
in your method name to indicate that the method can be awaited (regardless of whether you method’s implementation uses async
or not).
Task
should be returned for methods that would, if they were synchronous methods, return void
. Task<T>
should be returned for methods that would otherwise return some type T
(i.e., anything not void
). Think of the Task
as the object the caller can use to keep track of what ever happened to that asynchronous method he triggered.
What effect does async have?
Writing async
will do two things to your method or lambda:
- It will allow you to write
await
in your method (see my next blog post in this series). - If your return type is not
void
the compiler will automagically convert yourreturn
statement (or the missingreturn
statement at the end of your method) into aTask<T>
orTask
.
For a method that does not contain any await
calls, this means a completed Task
will be returned without the need to explicitly specify this. For the above example this means it will behave exactly like this non-async
version of it:
For a method that does contain an await
in the executed path, the method will return a Task
object that will turn to the IsCompleted
state when the last awaited call has completed and the synchronous code following it (if any) has finished executing. (For more on this, read my next blog post in this series about the await
keyword.)
Do I need async?
Methods containing only one await
statement as the very last instruction in the method may generally be written without the async
keyword. For example, the method
is equivalent to
Even though this yields the same result, the method declared as async
seems to be the more readable version and has a slight runtime penalty. The other difference here is that if stream.FlushAsync()
throws an exception and await
is not used FlushTheStreamAsync()
will not appear in the exception’s call stack (more on exceptions in the next blog post).
How does that help me?
As mentioned earlier, the returned Task
object can be used to examine the state of the asynchronous call (still running? completed? failed? cancelled?). While this is possible using the various methods of the Task
class, the easiest way to handle this is through the await
keyword handled in the next blog post.
One reply on “async/await part 1: Understanding the async keyword”
Methods containing only one await statement as the very last instruction in the method may generally be written without the async keyword