# Async/Await - Why?
While the TPL can be used to do parallel programming, its main use-case is to do asynchronous programming.
Asynchronous programming is concerned about doing operations that take up a certain amount of time to complete.
We don’t want to do blocking operations that would freeze a UI or would hinder a service serving other requests.
# Asynchronous use-cases
Common scenarios for asynchronous programming include:
- I/O-bound needs
- Requesting data from a network
- Accessing a database
- Reading/writing to a filesystem
- CPU-bound needs
- Performing an expensive calculation
# Asynchronous CSharp
C# has a language-level asynchronous programming model, meaning that asynchronous programming is supported by language constructs.
This allows us to easily write asynchronous code without having to manage callbacks or conform to a library and its methods.
# Task
A Task
or Task<T>
is used to represent an operation that takes a certain amount of time and is not completed immediately.
When executing a task on a background thread we want to start the operation, wait for completion, but while waiting being able to perform other operations.
The caller should not be blocked from doing other work.
# Keywords
In C# we can use the keywords async and await to deal with Tasks.
- For I/O-bound code, you await an operation that returns a
Task
orTask<T>
inside of anasync
method. - For CPU-bound code, you await an operation that is started on a background thread with the
Task.Run
method.
# Await
The await
keyword is where the magic happens. It yields control to the caller of the method that performed await
, and it ultimately allows a UI to be responsive or a service to be elastic.
When you write code with await
- then the Compiler creates a new class
# Example
Methods that handle asynchronous code have to be marked with the keyword async.
When calling asynchronous methods that return a Task
or Task<T>
, we use the await keyword.
Asynchronous methods should be named with the postfix Async (e.g. GetFromJsonAsync
)
# Sample Usage in a UI application
# Sample Usage - Expensive Calculation
# What happens under the covers
On the C# side of things, the compiler transforms your code into a state machine that keeps track of things like yielding execution when an await
is reached and resuming execution when a background job has finished.
# Compiler Magic
will be transformed to
# State Machine Class
Handles the execution.
For every async method, the compiler will automatically generate a new class - the state machine, that handles the execution. Local variables will be stored inside the class.
Notice that the async and await keywords are gone after the transformation. They are just syntactic sugar (compiler reformats code) that are unknown to the runtime.
# The generated state machine
# Generated state machine
The delay
and arg2
parameters are now fields on the state machine class and the logic that was in the original PrintAndWait()
method is now inside the MoveNext()
method of the state machine.
The generated state machine works by storing the current context (State) of the method so that it can be resumed after finishing it’s long running await tasks. Inside the PrintAndWaitStateMachine.MoveNext()
method we can see several checks for the current State (num
) value and calls to the method Builder.AwaitUnsafeOnCompleted()
# States
- -2: The result of the method is computed, or it has thrown; we can really return now, and never come back
- -1: Start of “await Task.Delay(delay)”
- If it completed instantly, or if it done, keep going.
- If it hasn’t completed, wait till it ends, and return.
- 0 … N: These are generated based on the number of
await
keywords used in the original method.- In the code above only 2 awaits are used so states 1 & 2 are present in the StateMachine
# Further information
- Overview of the state machine (main source of the last couple of slides):
- Deep Dive Explanation of what is going on:
# Dos and don’ts
We will have a look at a few things to avoid aswell as patterns that should be applied.
For more detailed information have a look at Async Guidance by David Fowler
# Asynchrony is viral
It’s very hard to avoid asynchronous methods as their usage has spread rapidly in the .NET Framework and Third Party Packages.
Once you go async, all of your callers should be async too. If only part of your call stack is async, the gains are close to zero.
Even worse partial asynchrony can be worse than being all synchronous.
# Bad
This example uses the Task.Result
and as a result blocks the current thread to wait for the result. This is an example of sync over async.
# Good
This example uses the async and await keywords instead.
# Never do async void
Use of async void methods is always bad.
Async void methods will crash the process if an exception is thrown.
Typically what you want to do when using async void is to do a fire and forget operation. Await an async method or just use Task.Run
. Every async method should return a Task
or Task<T>
object.
# Bad
# Good
# Use Task.FromResult for trivially computed values
If we know the result right away, or the result can be easily computed, there is no need to schedule a Task on the thread pool.