I'm trying to fully understand async
-await
and one of the gaps in my understanding is seeing what is "All the Way Down." I create an async
method, it is called by another async
method, etc., all the way down to something that I understand in vague terms like "a UI" or "a web server that can handle multiple requests". How would I describe in technical terms what is "all the way down"?
So let's take the second example of a web server. Say I have a controller action like
[HttpGet]
public async Task<IHttpActionResult> GetRecords()
{
var records = await repository.GetRecordsFromDbAsync();
return Ok(records);
}
Where can I find in the .NET source code the "all the way down" code that enables this to be called asynchronously?
It is not clear from your question where you found that quotation, or in what context it is being used. However, "async all the way down" is generally understood to mean that you shouldn't switch back and forth between synchronous and asynchronous methods in a single logical operation.
If something is to be done asynchronously, it should be consistently asynchronous, at all levels—thus, "all the way down" (and up) the hierarchy/call stack/etc. In other words, it never makes sense to do a synchronous, blocking call at any point in asynchronous code, because then it isn't really asynchronous at all.
From the .NET Parallel Programming team's blog entry, "Should I expose synchronous wrappers for asynchronous methods?":
As for your question about where can this be found in the .NET source code, MSalters has already answered this. Fundamentally, the .NET APIs will call down to system-level APIs provided by the Windows operating system. These perform asynchronous I/O, and signal the caller when they have completed. You don't really need to understand how this works on a technical level, though; that is the whole point of the abstraction.
The phrase "async all the way down" is a bit misleading, because it usually refers to the fact that once you use an async method, you need to have async methods all the way up (or back, depending on your mental image) - from your async method to its caller, and then your caller's caller, and so forth, all the way back.
Your example shows a WebApi/MVC controller that exposes an
async
task. The next step in theasync
chain is the WebApi/MVC infrastructure that receives the HTTP GET request, maps it to a controller, and dispatches the call to the controller's method. This infrastructure isasync
aware, meaning it knows to callasync
controller methods properly andawait
their result to return the HTTP response.As to how exactly that infrastructure is implemented, I don't specifically know, nor care - I know that ASP.NET web services support
async Task
controllers, and that's good enough for me.GetRecordsFromDbAsync
is an async method, so your top level async method (which is called by the async-supporting ASP.NET web server) just hands over its asyncness to the next level.All the way down until
GetRecordsFromDbAsync
or its descendants actually calls the last async method in the call stack. There it presumably becomes native and will it register an I/O interrupt or something else which gets called when the file is read, the web request is handled, etc.Every procedural programming language is a series of function / procedure calls, with one function / procedure calling another. It is possible to represent this sequence of calls from procedure to procedure using a call graph see Wikipedida for a starting point for call graphs. These graphs generally show the sequence of procedural calls starting at the top and proceeding to the bottom.
I quickly produced a diagram of a partial call graph for an ASP .NET MVC application. The diagram is only a partial representation for an ASP .NET MVC application because it omits things such as the initial receipt of the request by the operating system (eg. Windows), web server (eg. IIS) and various components of ASP .NET that are responsible for processing a HTTP request as it travels through the ASP .NET Request processing pipeline. These matters can can be omitted for the purposes of this discussion. Although it's worthwhile noting that they would sit at the top of the call graph, because they deal with the initial stages of handling the HTTP request, and ultimately, at some point ASP .NET ultimately invokes an action of a controller.
As you can see in the diagram I've represented the action of the controller that would be invoked by ASP .NET as an async action. On the left hand side of the call graph is a series of async procedure calls. Ultimately it arrives at the box that is the subject of your question.
The answer to the question that is consistent with the notion "All the way down" is that "I am an async method". What does this async method do? Well, that depends on what you want to do? If you're reading or writing a file then it's an async call to read or write a file. Are you making a database query? Then the call is to an async method that makes that query. With this in mind I guess you can say that often what is at the bottom will be a device driver method, that performs an IO access asynchronously. Although it could easily be a long running compute bound asynchronous operation that you wish to perform such as processing an image, or video file.
It's worthwhile noting the right hand side of the call graph here too. Although often you may want to call async methods all the way down, the right hand branch of this call graph shows that you don't necessarily need to call an async method at the bottom at all.
All the way down would be the Win32 API. At that level, async I/O is done via
OVERLAPPED
objects, and alertable waits such asWaitForMultipleObjectsEx
.