The State Machine in Async
Previously [Part 1] The Stub Method of Async
The state machine does exactly what it is supposed to – keeps track of the state when the execution reaches await
. When await
is reached, everything about where we are in the method is preserved, so that it can be resumed when the method is awaiting.
Instead of copying all local variables, which could potentially result in a lot of generated code, the compiler changes all the local variables to member variables and stores the instance of the type. Obviously, in case of static async
methods, there is no instance, so it would be omitted. The state machine is generated as an inner struct
nested under the type, giving it access to the private
members of that type.
Below is the example of what member variables would be generated for <GetStringAsync>d__0
:
public int <>1__state;
public AsyncTaskMethodBuilder<string> <>t__builder;
public AsyncStringClass <>4__this;
public string value;
public string <result>5__1;
private TaskAwaiter <>u__$awaiter2;
private object <>t__stack;
Description of Member Variables
The <>1__state
variable to store the await
count we have reached. Initially, before any await
is reached, its value is -1
. Each await
in async
method is numbered and the number of the current await
is written to this member variable.
Next is the <>t__builder
, which is helper type containing the logic that all state machines share. It creates the Task
that is returned by the method. The Task
is created similarly to how you would create a Task
from TaskCompletonSource.
The only difference here is that it is optimised for async
methods and is implemented as a struct
for performance reasons. If you are also curious about the implementation details of AsyncTaskMethodBuilder the source code, again can be found here.
You would notice that this file contains some other helper types.
The <>__this
variable contains the object that contains our async
method. For static async
methods, this variable will not be generated (as stated previously). The knowledge of the type will suffice. After the async
transformation, it needs to be stored and the code would have moved away from its original method and the object.
The value variable is nothing more than the reference to our string
(I almost wrote a ‘copy of our string’ here). All accesses to our value method parameter will be replaced by value stored in the generated struct
.
Exactly the same logic applies to the result method variable, which is stored in the struct as <result>5__1
.
The variable <>u_$awaiter2
is a variable of type TaskAwaiter.
It is used as a temporary storage for the object that is used by the await
keyword to sign up for notification when the Task
finishes.
Finally note, that the state machine keeps track of the stack in the member variable <>t__stack
. All the current values are placed in this variable. If there is more than 1
, then the values are placed inside a Tuple.