2

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?

Zach
  • 5,715
  • 12
  • 47
  • 62
  • So you expect that [some thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) is blocked during the awaiting of a `Task`? – Theodor Zoulias Nov 27 '21 at 03:34
  • I don't quite follow the question, but if it is any use - the C# compiler rewrites async/await into a state machine (it's really just a switch statement) which it will then call repeatedly in an event based way. There isn't a blocked thread/a thread to wake up again as such. – Luke Briggs Nov 27 '21 at 03:35
  • @LukeBriggs allow me to note that the comments section [is intended](https://prnt.sc/20xifgd) for asking clarifications about the question, not for answering it! – Theodor Zoulias Nov 27 '21 at 03:39
  • What async & await does is wrap the code following the awaited task in a _continuation_ (think a lambda) and then the method returns information about that continuation in the Task the method returns. When the awaited Task completes, the continuation is invoked (depending on the _awaiter_, possibly on the original thread). At some point, the continuation completes and it signals Task the method returned as complete and then await one up on the stack does the same little dance. Find blog entries by Stephen Cleary, Stephen Toub and Eric Lippert for better explanations – Flydog57 Nov 27 '21 at 03:39
  • @TheodorZoulias if that was posted as an answer, it would be downvoted as it of course does not answer the question - rather just gives some info which might help the OP ask something a bit clearer :) – Luke Briggs Nov 27 '21 at 03:41
  • 1
    @LukeBriggs yeap, hopefully the OP will make clearer what they are asking. The question seems to be "Where is the `Wait`?", which assumes that there is a `Wait` somewhere. IMHO a better question would be "Is there a `Wait`? And if it is, where it is?" – Theodor Zoulias Nov 27 '21 at 04:07
  • Diving first to the state machine is the key to unlock the magic around async/await, https://www.red-gate.com/simple-talk/development/dotnet-development/c-async-what-is-it-and-how-does-it-work/ – Lex Li Nov 27 '21 at 06:31
  • 1
    @TheodorZoulias yes you are correct. My question is exactly what you are saying. Is there a `Wait`? And if it is, where it is? – Zach Nov 27 '21 at 07:07
  • @Zach **No**, there is no `Wait()` - nor is there any kind of thread-blocking (at least, not unless a program does something silly), The whole "`async`/`await`" model works on a similar basis to how _cooperative multitasking_ worked in decades past: if a thread cannot do any more work _for now_ then it (basically) saves its state (_continuation_) and lets the runtime (.NET) reuse it for something completely different. – Dai Nov 27 '21 at 07:10
  • @Zach See my answer here: https://stackoverflow.com/a/62724143/159145 which gives a step-by-step explanation of how `await` is effectively compiled by `csc` and how it works in a running program. – Dai Nov 27 '21 at 07:13
  • @Dai I know the winforms UI will block at 'GetMessage()' function. So maybe the `StateMachine` itself does not wait, but instead, the UI waits. When a `task` completes, it posts a message to the queue, and `GetMessage()` returns to call `StateMachine.MoveNext()`. Am I right? – Zach Nov 27 '21 at 07:33
  • @Zach WinForms doesn't call `GetMessage()` such that it will block, instead it checks with `PeekMessage()` first. Look at the disassembly of `System.Windows.Forms.Application.ThreadContext.LocalModalMessageLoop`. – Dai Nov 27 '21 at 07:36
  • @Dai there is a line `UnsafeNativeMethods.WaitMessage();` at line 3567, at https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,3459 – Zach Nov 27 '21 at 07:40
  • Quick question, do you understand `yield return`? – Caius Jard Nov 27 '21 at 07:50
  • @CaiusJard not really, could you give a short explanation? – Zach Nov 27 '21 at 07:53
  • @Dai yes, I read the `FPushMessageLoop()`. In short, it first calls `PeekMessage()` to check if any message in the queue. If no message, it calls `WaitMessage()` finally which blocks the UI – Zach Nov 27 '21 at 08:05
  • @Zach I checked just now and what actually happens is **yes** the `FPushMessageLoop()` method does indeed block until the next message is received, but that's okay because whenever you use `await` in WinForms it unblocks `FPushMessageLoop` by first sending a window-message (with a name like `"WindowsForms12_ThreadCallbackMessage"` ) which allows the continuation to _almost_ immediately run in the UI thread. – Dai Nov 27 '21 at 08:18
  • @Zach Note that what I'm describing is unique to WinForms due to WinForms' own `WindowsFormsSynchronizationContext`. When you don't have any WinForms code (and so no window message loop nor UI thread) there's still no waiting/blocking. – Dai Nov 27 '21 at 08:21
  • 1
    @Dai I think you are right. Many thanks – Zach Nov 27 '21 at 08:49

1 Answers1

4

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Intersting! Do you have any source explaining those consideration ? (on what is *correct* vs *incorrect* implementation) – XouDo Nov 27 '21 at 09:03
  • @XouDo For example, when a method has separate non-async and async versions of a method (e.g. `Stream.Read` and `Stream.ReadAsync`), but if the `async` version never actually uses `await` but instead does `return Task.FromResult( this.Read(...) );`, that is also known as "fake-async". – Dai Nov 27 '21 at 09:09
  • 1
    @XouDo https://markheath.net/post/async-antipatterns – Dai Nov 27 '21 at 09:10