A SynchronizationContext
allows posting work to a queue that is processed by another thread (or by a thread pool) — usually the message loop of the UI framework is used for this.
The async
/await
feature internally uses the current synchronization context to return to the correct thread after the task you were waiting for has completed.
The AsyncSynchronizationContext
class implements its own message loop. Work that is posted to this context gets added to a queue.
When your program calls WaitForPendingOperationsToComplete();
, that method runs a message loop by grabbing work from the queue and executing it.
If you set a breakpoint on Console.WriteLine("Done awaiting");
, you will see that it runs on the main thread within the WaitForPendingOperationsToComplete()
method.
Additionally, the async
/await
feature calls the OperationStarted()
/ OperationCompleted()
methods to notify the SynchronizationContext
whenever an async void
method starts or finishes executing.
The AsyncSynchronizationContext
uses these notifications to keep a count of the number of async
methods that are running and haven’t completed yet. When this count reaches zero, the WaitForPendingOperationsToComplete()
method stops running the message loop, and the control flow returns to the caller.
To view this process in the debugger, set breakpoints in the Post
, OperationStarted
and OperationCompleted
methods of the synchronization context. Then step through the AsyncMethod
call:
- When
AsyncMethod
is called, .NET first callsOperationStarted()
- This sets the
_operationCount
to 1.
- This sets the
- Then the body of
AsyncMethod
starts running (and starts the background task) - At the
await
statement,AsyncMethod
yields control as the task is not yet complete currentContext.WaitForPendingOperationsToComplete();
gets called- No operations are available in the queue yet, so the main thread goes to sleep at
_operationsAvailable.WaitOne();
- On the background thread:
- at some point the task finishes sleeping
- Output:
Done sleeping!
- the delegate finishes execution and the task gets marked as complete
- the
Post()
method gets called, enqueuing a continuation that represents the remainder of theAsyncMethod
- The main thread wakes up because the queue is no longer empty
- The message loop runs the continuation, thus resuming execution of
AsyncMethod
- Output:
Done awaiting
AsyncMethod
finishes execution, causing .NET to callOperationComplete()
- the
_operationCount
is decremented to 0, which marks the message loop as complete
- the
- Control returns to the message loop
- The message loop finishes because it was marked as complete, and
WaitForPendingOperationsToComplete
returns to the caller - Output:
Press any key to continue. . .