Home > front end >  Async/await: who is waiting/blocking?
Async/await: who is waiting/blocking?

Time:11-28

I'm trying to understand async/await and read the source code of AsyncMethodBuilder. I thought there must be some code like xxx.Wait() or xxx.WaitOnce() waiting for a task to be completed.

However, I didn't find such code in class AsyncMethodBuilder.

system\runtime\compilerservices\AsyncMethodBuilder.cs https://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs,96

So I keep digging, tried to read the source code of Task, TaskScheduler, ThreadPoolTaskScheduler, ThreadPool.

Finally I got class _ThreadPoolWaitCallback, but didn't find any caller. https://referencesource.microsoft.com/#mscorlib/system/threading/threadpool.cs,d7b8a78b4dd14fd0

internal static class _ThreadPoolWaitCallback
{
    [System.Security.SecurityCritical]
    static internal bool PerformWaitCallback()
    {
        return ThreadPoolWorkQueue.Dispatch();
    }
}

Another possible code is in class SynchronizationContext, method SetWaitNotificationRequired()

https://referencesource.microsoft.com/#mscorlib/system/threading/synchronizationcontext.cs,8b34a86241c7b423

 protected void SetWaitNotificationRequired()
    {
        ...
            RuntimeHelpers.PrepareDelegate(new WaitDelegate(this.Wait));
        ...

But I didn't know what RuntimeHelpers.PrepareDelegate is doing, which is a native method.

Please give some advice. Is there a Wait? And if it is, where it is?

CodePudding user response:

In a correctly implemented async implementation: there is no wait. Instead, at the bottom of the chain, some code exists that will create some async source, which could be a TaskCompletionSource<T>, an IValueTaskSource[<T>], or something similar - which allows that code to store that token somewhere (for example, in a queue, a correlation dictionary, or an async state object for IOCP), and return the incomplete task to the caller. The caller then discovers that it is incomplete, and registers a "when you have the answer, do this to reactivate me" callback. That calling code now unwinds completely, with every step saying "when you're done, push here", and the thread goes on to do other things, such as service a different request.

At some point in the future (hopefully), the result will come back - again, via IOCP, or via a separate IO reader pulling a response from somewhere and taking the appropriate item out of the queue/correlation-dictionary, and says "the outcome was {...}" (TrySetResult, TrySetException, etc).

For all of that time no threads were blocked. That is, ultimately, the entire point of async/await: to free up threads, to increase scalability.


In incorrectly implemented async systems: anything and everything is possible, including async-over-sync, sync-over-async, and everything else.

  • Related