Reputation: 9389
Here is the sample code from https://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4
public async Task<ActionResult> GizmosAsync()
{
ViewBag.SyncOrAsync = "Asynchronous";
var gizmoService = new GizmoService();
return View("Gizmos", await gizmoService.GetGizmosAsync());
}
As I understood, async/await is usefull for freeing a Worker Thread so it can process other request. But GetGizmosAsync is not only a call to "HttpResponse.Content.ReadAsAsync" (which I guess is executed by a thread from the IO pool) it's also calling other non async code like " var uri = Util.getServiceUri("Gizmos");".
What is executing "var uri = Util.getServiceUri("Gizmos");" if it's not a thread from the worker pool ? there is no IO here.
Upvotes: 2
Views: 196
Reputation: 239430
Your understanding of async is a little off. It allows the thread to be returned to the pool if it's in a wait-state. That last bit is important. The thread is still executing the code, it's just that some external process is going on, which currently doesn't require that thread, i.e. network comm, file I/O, etc. Once that external process completes, the OS is responsible for restoring control to the originating process, which then requires a new thread to be requested from the pool to continue.
Also, just because some part of your code is async doesn't mean everything happens async: namely only other code that is async and is actually eligible to be async. As I said above, the thread is only released if it's in a wait-state; running something sync would mean that it is not waiting, and therefore will not be released. Also, some things will never be async, even if you try to run them async. Anything CPU-bound requires the thread to run, so the thread will never enter a wait-state and will never be released.
UPDATE
The assumption seems to be that there's some other thread that's actually handling the async work, but that's not what's happening. Like I said in the comments, the OS-level work is extremely technical, but this is the best simplified explanation I've found. Relevant portion of the article is below for posterity:
What About the Thread Doing the Asynchronous Work?
I get asked this question all the time. The implication is that there must be some thread somewhere that’s blocking on the I/O call to the external resource. So, asynchronous code frees up the request thread, but only at the expense of another thread elsewhere in the system, right? No, not at all.
To understand why asynchronous requests scale, I’ll trace a (simplified) example of an asynchronous I/O call. Let’s say a request needs to write to a file. The request thread calls the asynchronous write method. WriteAsync is implemented by the Base Class Library (BCL), and uses completion ports for its asynchronous I/O. So, the WriteAsync call is passed down to the OS as an asynchronous file write. The OS then communicates with the driver stack, passing along the data to write in an I/O request packet (IRP).
This is where things get interesting: If a device driver can’t handle an IRP immediately, it must handle it asynchronously. So, the driver tells the disk to start writing and returns a “pending” response to the OS. The OS passes that “pending” response to the BCL, and the BCL returns an incomplete task to the request-handling code. The request-handling code awaits the task, which returns an incomplete task from that method and so on. Finally, the request-handling code ends up returning an incomplete task to ASP.NET, and the request thread is freed to return to the thread pool.
Now, consider the current state of the system. There are various I/O structures that have been allocated (for example, the Task instances and the IRP), and they’re all in a pending/incomplete state. However, there’s no thread that is blocked waiting for that write operation to complete. Neither ASP.NET, nor the BCL, nor the OS, nor the device driver has a thread dedicated to the asynchronous work.
When the disk completes writing the data, it notifies its driver via an interrupt. The driver informs the OS that the IRP has completed, and the OS notifies the BCL via the completion port. A thread pool thread responds to that notification by completing the task that was returned from WriteAsync; this in turn resumes the asynchronous request code. There were a few threads “borrowed” for very short amounts of time during this completion-notification phase, but no thread was actually blocked while the write was in progress.
This example is drastically simplified, but it gets across the primary point: no thread is required for true asynchronous work. No CPU time is necessary to actually push the bytes out. There’s also a secondary lesson to learn. Think about the device driver world, how a device driver must either handle an IRP immediately or asynchronously. Synchronous handling is not an option. At the device driver level, all non-trivial I/O is asynchronous. Many developers have a mental model that treats the “natural API” for I/O operations as synchronous, with the asynchronous API as a layer built on the natural, synchronous API. However, that’s completely backward: in fact, the natural API is asynchronous; and it’s the synchronous APIs that are implemented using asynchronous I/O!
Upvotes: 2