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 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
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
<>1__state variable to store the
await count we have reached. Initially, before any
await is reached, its value is
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
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.
<>__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
Exactly the same logic applies to the result method variable, which is stored in the struct as
<>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
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.