C# – What Happens When a Thread Awaits a Task Inside a While Loop?

asynccloops

After dealing with C#'s async/await pattern for a while now, I suddenly came to realization that I don't really know how to explain what happens in the following code:

async void MyThread()
{
    while (!_quit)
    {
        await GetWorkAsync();
    }
}

GetWorkAsync() is assumed to return an awaitable Task which may or may not cause a thread switch when the continuation is executed.

I wouldn't be confused if the await wasn't inside a loop. I'd naturally expect that the rest of the method (i.e. continuation) would potentially execute on another thread, which is fine.

However, inside a loop, the concept of "the rest of the method" gets a bit foggy to me.

What happens to "the rest of the loop" if the thread is switched on continuation vs. if it isn't switched? On which thread is the next iteration of the loop executed?

My observations show (not conclusively verified) that each iteration starts on the same thread (the original one) while the continuation executes on another. Can this really be? If yes, is this then a degree of unexpected parallelism that needs to be accounted for vis-a-vis thread-safety of the GetWorkAsync method?

UPDATE: My question is not a duplicate, as suggested by some. The while (!_quit) { ... } code pattern is merely a simplification of my actual code. In reality, my thread is a long-lived loop that processes its input queue of work items on regular intervals (every 5 seconds by default). The actual quit condition check is also not a simple field check as suggested by the sample code, but rather an event handle check.

Best Answer

You can actually check it out at Try Roslyn. Your await method gets rewritten into void IAsyncStateMachine.MoveNext() on the generated async class.

What you'll see is something like this:

            if (this.state != 0)
                goto label_2;
            //set up the state machine here
            label_1:
            taskAwaiter.GetResult();
            taskAwaiter = default(TaskAwaiter);
            label_2:
            if (!OuterClass._quit)
            {
               taskAwaiter = GetWorkAsync().GetAwaiter();
               //state machine stuff here
            }
            goto label_1;

Basically, it doesn't matter which thread you're on; the state machine can resume properly by replacing your loop with an equivalent if/goto structure.

Having said that, async methods don't necessarily execute on a different thread. See Eric Lippert's explanation "It's not magic" to explain how you can have working async/await on only one thread.

Related Topic