Coder Perfect

Why is the internal “state” set to 0 in generated code before the EndAwait call in C# 5 async CTP?

Problem

Yesterday I was giving a talk about the new C# “async” feature, in particular delving into what the generated code looked like, and the GetAwaiter() / BeginAwait() / EndAwait() calls.

We looked at the state machine built by the C# compiler in depth, and there were two parts that we couldn’t figure out:

I believe the first concern can be addressed by doing something more interesting with the async function, but if anyone has any other insight, I’d appreciate hearing it. However, the focus of this question is on the second point.

Here’s a short piece of code to get you started:

using System.Threading.Tasks;

class Test
{
    static async Task<int> Sum(Task<int> t1, Task<int> t2)
    {
        return await t1 + await t2;
    }
}

… and here’s the code that’s created for the state machine’s MoveNext() method. This is a verbatim copy from Reflector; I haven’t changed the obscene variable names:

public void MoveNext()
{
    try
    {
        this.$__doFinallyBodies = true;
        switch (this.<>1__state)
        {
            case 1:
                break;

            case 2:
                goto Label_00DA;

            case -1:
                return;

            default:
                this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
                this.<>1__state = 1;
                this.$__doFinallyBodies = false;
                if (this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate))
                {
                    return;
                }
                this.$__doFinallyBodies = true;
                break;
        }
        this.<>1__state = 0;
        this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
        this.<a2>t__$await4 = this.t2.GetAwaiter<int>();
        this.<>1__state = 2;
        this.$__doFinallyBodies = false;
        if (this.<a2>t__$await4.BeginAwait(this.MoveNextDelegate))
        {
            return;
        }
        this.$__doFinallyBodies = true;
    Label_00DA:
        this.<>1__state = 0;
        this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
        this.<>1__state = -1;
        this.$builder.SetResult(this.<1>t__$await1 + this.<2>t__$await3);
    }
    catch (Exception exception)
    {
        this.<>1__state = -1;
        this.$builder.SetException(exception);
    }
}

Although it is lengthy, the following are the key lines for this question:

// End of awaiting t1
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

// End of awaiting t2
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();

In both circumstances, the status is changed again before it is next clearly detected… so why set it to 0 in the first place? If MoveNext() is called again at this time (either directly or via Dispose), the async function will be restarted, which is completely wrong in my opinion… if and MoveNext() isn’t called, the state change is useless.

Is this just a result of the compiler recycling the iterator block generation code for async, or is there a more evident explanation?

Important disclaimer

This is obviously just a CTP compiler. Before the final release – and possibly even before the next CTP release – I absolutely anticipate things to change. This inquiry is not intended to imply that there is a problem in the C# compiler or anything. I’m just trying to figure out if there’s a more subtle cause for this that I’m overlooking:)

Asked by Jon Skeet

Solution #1

Okay, I’ve finally gotten a response. I figured it out on my own, but only after Lucian Wischik of the VB team confirmed that there is a good explanation for it. Many thanks to him, and be sure to check out his blog (on archive.org), which is fantastic.

In a regular instance, the value 0 is merely exceptional because it isn’t a valid state to be in shortly before the await. It’s also not a state that the state machine might wind up testing for anywhere else. Any non-positive value, I believe, would work just as well: -1 isn’t utilized because it’s illogical because -1 generally signifies “completed.” I could argue that we’re currently giving state 0 an extra significance, but in the end, it doesn’t really matter. The goal of this query was to figure out why the state was being set in the first place.

If the await results in an exception that is caught, the value is relevant. We may return to the same await statement, but we must not be in the condition of “I’m about to return from that await,” as this would cause all types of code to be skipped. It’s easiest to demonstrate this with an example. Because I’m using the second CTP, the resulting code differs significantly from the code in the query.

The async method is as follows:

static async Task<int> FooAsync()
{
    var t = new SimpleAwaitable();

    for (int i = 0; i < 3; i++)
    {
        try
        {
            Console.WriteLine("In Try");
            return await t;
        }                
        catch (Exception)
        {
            Console.WriteLine("Trying again...");
        }
    }
    return 0;
}

In theory, the SimpleAwaitable can be any awaitable – a task, for example, or something else entirely. For the purposes of my tests, IsCompleted always returns false, and GetResult always raises an exception.

The code for MoveNext is as follows:

public void MoveNext()
{
    int returnValue;
    try
    {
        int num3 = state;
        if (num3 == 1)
        {
            goto Label_ContinuationPoint;
        }
        if (state == -1)
        {
            return;
        }
        t = new SimpleAwaitable();
        i = 0;
      Label_ContinuationPoint:
        while (i < 3)
        {
            // Label_ContinuationPoint: should be here
            try
            {
                num3 = state;
                if (num3 != 1)
                {
                    Console.WriteLine("In Try");
                    awaiter = t.GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        state = 1;
                        awaiter.OnCompleted(MoveNextDelegate);
                        return;
                    }
                }
                else
                {
                    state = 0;
                }
                int result = awaiter.GetResult();
                awaiter = null;
                returnValue = result;
                goto Label_ReturnStatement;
            }
            catch (Exception)
            {
                Console.WriteLine("Trying again...");
            }
            i++;
        }
        returnValue = 0;
    }
    catch (Exception exception)
    {
        state = -1;
        Builder.SetException(exception);
        return;
    }
  Label_ReturnStatement:
    state = -1;
    Builder.SetResult(returnValue);
}

I had to change Label ContinuationPoint to make it proper code (it wasn’t in the scope of the goto statement otherwise), but this has no bearing on the answer.

Consider what occurs when GetResult throws an exception. We’ll loop around again after going through the catch block and incrementing i. (assuming I is still less than 3). We’re still in the same state as before the GetResult call… but once we’re inside the try block, we’ll have to print “In Try” and call GetAwaiter again… and we’ll only do that if state isn’t 1. It will use the existing awaiter and skip the Console if state = 0 is not specified. Call to WriteLine

It’s a difficult piece of code to decipher, but it demonstrates the types of issues that the team must consider. I’m glad I’m not in charge of putting this in place:)

Answered by Jon Skeet

Solution #2

if it was kept at 1 (first case) you would get a call to EndAwait without a call to BeginAwait. If it’s kept at 2 (second case) you’d get the same result just on the other awaiter.

Calling the BeginAwait returns false if it has already been started (a guess on my part) and preserves the original value to return at the EndAwait. If that’s the case, it’ll work as expected, however setting it to -1 may result in an uninitialized this. <1> For the first scenario, use t $await1.

This presupposes, however, that BeginAwaiter will not actually start the action on any subsequent calls and will return false in those situations. Starting would, of course, be unacceptable because it could have unintended consequences or produce a different outcome. It also assumes that no matter how many times EndAwait is called, it will always return the same value, and that it can be invoked while BeginAwait returns false (as per the above assumption)

It appears to be a safeguard against racial tensions. If we put the statements where movenext is called by a different thread after the state = 0 in questions inline, it will look like this.

this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
this.<>1__state = 1;
this.$__doFinallyBodies = false;
this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate)
this.<>1__state = 0;

//second thread
this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
this.<>1__state = 1;
this.$__doFinallyBodies = false;
this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate)
this.$__doFinallyBodies = true;
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

//other thread
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

If the following assumptions are valid, some work is done that isn’t necessary, such as getting sawiater and reassigning the same value to 1>t $await1. If the state was maintained at 1, the final part would be:

//second thread
//I suppose this un matched call to EndAwait will fail
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

Furthermore, if it was set to 2, the state machine would presume it had already acquired the value of the first action, which would be false, and the result would be calculated using a (possibly) unassigned variable.

Answered by Rune FS

Solution #3

Is it possible that it has to do with stacked/nested async calls? ..

i.e:

async Task m1()
{
    await m2;
}

async Task m2()
{
    await m3();
}

async Task m3()
{
Thread.Sleep(10000);
}

In this case, does the movenext delegate get called numerous times?

Isn’t it just a gamble?

Answered by GaryMcAllister

Solution #4

Actual state explanation:

possible states:

Is it feasible that this solution simply wishes to ensure that if another Call to MoveNext occurs (while waiting), it will reconsider the entire state-chain from the beginning, reevaluating results that may have been obsolete in the meantime?

Answered by fixagon

Post is based on https://stackoverflow.com/questions/5027999/c-sharp-5-async-ctp-why-is-internal-state-set-to-0-in-generated-code-before-e