3

When an await is encountered by a running thread, what mechanism is used internally to "bookmark" that that particular method will eventually need to resume when the awaited task is done? Consider the following method:

public async void DoSomething()
{
   await Task.Run(() => SomeLongRunningWork());
   // Code to resume when the task is done...
   Console.WriteLine("Resuming...");
}

When the above await is reached, a thread on the thread pool is used to do the long running work. Furthermore, the calling thread immediately exits the DoSomething() method, and will "come back" when the task is done.

How does the calling thread know how to "come back" at some point? What is happening internally to accomplish this?

Struessel
  • 41
  • 3
  • 3
    In really rough terms, the compiler wraps the code after the `await` into a *continuation* and passes it to the task as the code that should be run after the task completes. It's more complicated than that (for example, having the *awaiter* figure out which thread the continuation should run on, how exceptions are handled, etc.). There are good blogs and MSDN articles that came out right as `async`/`await` was about to be released that go into greater detail than my rough description – Flydog57 Jul 04 '20 at 02:31
  • 1
    One of such articles there : https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-1-compilation – Roman Kalinchuk Jul 04 '20 at 02:49

1 Answers1

3

When a method contains an await, the compiler creates what is called an Async State Machine. The task being awaited is stored off and everything after the await is marked as the task's continuation.

Through the state machine, an AsyncTaskMethodBuilder<>, and a TaskAwaiter<>, the infrastructure keeps track of the execution and synchronization contexts, captures local variables and ultimately tracks the state of the original Task while it waits for completion. It does this by looping (calling an internal MoveNext method) until the task has completed, been cancelled or faulted (due to an exception). Assuming the former, the continuation is then invoked on the appropriate context. When an exception is thrown, the AggregateException you would normally get with a Task is "unwrapped" (throwing the first exception) to mimic the experience of a normal, synchronous method.

This is an overall simplification. There are various optimizations and a bunch of machinery that makes this all work. For a good blog on the topic, I suggest this Microsoft series on async methods.

pinkfloydx33
  • 11,863
  • 3
  • 46
  • 63