C# Asynchronous Invocation
Asynchronous method is implemented in C# by using async/await
keywords.
Async/Await keywords
That’s the goal of asynchronous: enable code that reads/writes like a sequence of statements, but executes in a much more complicated order based on external resource allocation and when tasks complete.
Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. In C#, keywords async
and await
are used to achieved a asynchronous program. The function return immediately when it meets a await
. A code sample is like following.
1 | using System; |
How to code executed:
- Run Method1, code output
method1
, then encounterawait
, get back to the main function - Run Method2, code output
method2
, then encounterawait
, get back to the main function - Run Method3, since the running time is less than 330 milliseconds, it will output all the content in Method3
- After 330 milliseconds from Method1 returning, Method1 output the next str
- After 330 milliseconds from Method2 returning, Method2 output the next str
- Repeat 4 and 5 until output all string.
Note. the step 4 and 5 won’t output if we don’t add the ReadKey
method in the end because the main thread will ends and those asnyc methods still in the same thread thus end, too.
Analysis of metadata
In IL, we find that the compiler generate 2 classes for Method1 and Method2 called <Method1>d__0
and <Method2>d__1
. The two classes are state machines, the implement the interface TStateMachine
. The class contains the following elements.
- Fields
int
stateTestAsync
thisAsyncVoidMethodBuilder
builderTaskAwaiter
u__1int
5__1 (the local inti
in for loop)
- Methods
.ctor
- MoveNext
- SetStateMachine
There are two important data types. System.Runtime.CompilerServices.AsyncVoidMethodBuilder
and System.Runtime.CompilerServices.TaskAwaiter
- AsyncVoidMethodBuilder
- A struct, value type
- Represents a builder for asynchronous methods that do not return a value.
- Its
Create
method will return a instance of the builder - Its
Start<TStateMachine>
method will begin running the builder with the associated state machine
- TaskAwaiter
- A struct, value type
- Provides an object that can check whether current asynchronous task is completed and waits for the completion of an asynchronous task
- Properties
- IsCompleted. Gets a value that indicates whether the asynchronous task has completed.
- Methods
- GetResult. Ends the wait for the completion of the asynchronous task.
- OnCompleted(Action). Sets the action to perform when the TaskAwaiter object stops waiting for the asynchronous task to complete.
- UnsafeOnCompleted(Action). Schedules the continuation action for the asynchronous task that is associated with this awaiter.
IL execution pipeline
For the Method2 itself, the IL code is as follows
Once we call the Method2, it does the following things.
- Initializes a class called
<Method2>d__1
. - Assign the current instance to
<Method2>d__1::this
- Call
AsyncVoidMethodBuilder::Create<<Method2>d__1>
to create a async void instance for the state machine - Set
<Method2>d__1::state
to -1 - Call
AsyncVoidMethodBuilder.Start
, the passing state machine is the class<Method2>d__1
- Call the
<Method2>d__1::MoveNext
, since the state is -1, output the str, and change state to 0 - Once the state is set to 0, initialize the
5__1
field as 0, then call theTask.Delay
method, and get awaiter for this task and set it tou__1
field - Check
u__1::IsCompleted
. If haven’t finish, callAsyncVoidMethodBuilder::AwaitUnsafeOnCompleted<5__1, <Method2>d__1>
, which will call<Method2>d__1::MoveNext
method when the status of the awaiter is complete- Note. I think the AsyncVoidMethodBuilder will set the
<Method2>d__1::MoveNext
to5__1::UnsafeOnCompleted(Action)
- Note. I think the AsyncVoidMethodBuilder will set the
- The next time it call the MoveNext method, the code will execute different logic since the state has been changed.
Asynchronous method return types
Async methods can have the following return types
| void | Task | Task<TResult>
—–|——|——|—————
has return statement | No | Yes | Yes
return statement type | - | Task | Task<TResult>
can assign to delegate or event | Yes | Yes | Yes
can assign to Task | No | Yes | Yes
caller can know whether this method has completed (create a awaiter for it) | No | Yes | Yes
its awaiter has return statement | - | No | Yes
its awaiter return type | - | - | TResult
Task and Task class
Compared to execute a thread to do actions, the Task
class is used to execute asynchronously, and it can track the status of this asynchronous action, including whether it finish and what is the return value. Most commonly, a lambda expression is used to specify the work that the task is to perform.
For operations that return statement, the Task<TResult>
class will be used.
Samples
1 | using System; |
In the mMain1 method, the Method1 and Method2 are still be executed in a synchronously. So the output is the same with the void-returning async method.
In the mMain2 method, we create the awaiter for the Method1 and Method2, so Method2 will be called after Method1, and Method3 will be called after Method2.
In the mMain3 method, we firstly run the Method1 and Method2 synchronously and assign them to Task and Task<TResult> variables. So the two methods will run at the same time. Then we use Task.WaitAny
and create a awaiter for Method2, so Method3 will run after Method1 and Method2.
Analysis in IL
Similarly, the complier will generate 2 state machine classes for Method1 and Method2 and 3 state machine for mMain1, mMain2, mMain3.
In the state machine of Method1 and Method2, it is similar to the void-returning async method but it uses AsyncTaskMethodBuilder<TResult>
classes to build the async task. In Method2, when it is finished, it uses SetResult(TResult)
method to marks the task as successfully completed and also pass the ReturnArgs
statement.
In the mMain2 and mMain3 state machines, there will be TaskAwaiter
class and TaskAwaiter<TResult>
result. After the awaiter’s property IsCompleted
is true, they will call TaskAwaiter<TResult>.GetResult
to get the return statement from the AsyncTaskMethodBuilder<TResult>
.
Cancellation token to terminate async thread
The Async function can be cancelled by passing a CancellationToken
parameter created by a CancellationTokenSource
.
1 | CancellationTokenSource tokenSource; |