Home > front end >  .NET Async / Await: How the State Machine knows when to continue it's execution?
.NET Async / Await: How the State Machine knows when to continue it's execution?

Time:08-29

So i've read a lot of articles lately by trying to understand how the async methods works in the .NET world.

I've tried really hard to understand how an async call works, from actually calling HttpClient.GetAsync("https://google.com"), all the way down to how the OS initiates the call on the network card. What bothers me is how the state machine knows when to call it's MoveNext() method.

From the network card, we have a system interrupt (once we have the request response), which seizes the control over the CPU for a brief moment. The CPU creates a so called Deferred Procedure Call (DPC) which the OS kernel keeps tracks of and executes these DPC's with high priority. After that the kernel initiates an APC (asynchronous procedure call) which, in bigger words, notifies the thread which made the async call and marks the Task as completed, and can resume it's execution.

My question is:

In a windows forms application, we have a UI Thread (which draws the window on the screen and responds to user events). Does this UI Thread have some sort of a Task queue which keeps on interogating from time to time, and asks if one of the tasks in the Completed state ? If yes, then everything makes sense, and my original question is answered. Once the OS sets the tasks as completed, and the UI thread sees that, it will call the MoveNext() method on it's State Machine. But i heard recently that the UI Thread doesn't have this kind of Task queue. If this is the case, then how the OS, and internally the app itself, knows how to trigger the MoveNext() method ?

One assumption that i made, is that the OS injects the code of MoveNext() in whatever thread is currently available. But it still weird.

CodePudding user response:

marks the Task as completed, and can resume it's execution.

Back up a bit first. I recommend reading this blog entry of mine if you haven't already.

When an async method performs an await, it first checks whether the operation is complete. In this case, it generally wouldn't be (but it's unwise to assume this; mobile platforms in particular heavily cache network requests and can complete them immediately). So let's assume the task returned from GetAsync is incomplete; in that case, the async method registers a continuation on that task.

But it's not just registering MoveNext; by default, async methods capture their current context (SynchronizationContext or TaskScheduler), and register a delegate that continues executing the async method on that context. If there is no context, then the continuation is scheduled to the thread pool.

But i heard recently that the UI Thread doesn't have this kind of Task queue. If this is the case, then how the OS, and internally the app itself, knows how to trigger the MoveNext() method ?

That is correct. The UI thread does not have any notion of a task queue or what kinds of tasks it's waiting for. But it does have a message queue; this is the "main loop" of a UI thread. The OS sends it messages about mouse clicks and keyboard presses and a hundred other things besides. And it also supports custom (application-defined) messages. One of those custom message types is "run this delegate code on the UI thread".

The UI thread also has a SynchronizationContext - check out SynchronizationContext.Current in the debugger sometime. And this SynchronizationContext can be used to schedule work on the UI thread by sending itself a custom message to execute a specific delegate.

So, the async method will capture the current SynchronizationContext if present, and use that to schedule the continuation of the method; since it's a UI context, this means the continuation is sent as a message to the message loop, where the UI thread will (eventually) process it, continuing the async method on the UI thread.

  • Related