简体   繁体   English

深入理解ASP.NET MVC上的async / await

[英]Deep understanding of async / await on ASP.NET MVC

I don't understand exactly what is going on behind the scenes when I have an async action on an MVC controller especially when dealing with I/O operations. 当我在MVC控制器上进行异步操作时,尤其是在处理I / O操作时,我并不完全理解幕后发生了什么。 Let's say I have an upload action: 假设我有一个上传动作:

public async Task<ActionResult> Upload (HttpPostedFileBase file) {
  ....
  await ReadFile(file);

  ...
}

From what I know these are the basic steps that happen: 据我所知,这些是发生的基本步骤:

  1. A new thread is peeked from threadpool and assigned to handle incomming request. 从线程池中查看新线程并分配给处理命令请求。

  2. When await gets hit, if the call is an I/O operation then the original thread gets back into pool and the control is transfered to a so-called IOCP (Input output completion port). 当await被命中时,如果调用是I / O操作,则原始线程返回池并且控制转移到所谓的IOCP(输入输出完成端口)。 What I do not understand is why the request is still alive and waits for an answer because in the end the calling client will wait for our request to complete. 我不明白为什么请求仍然存在并等待答案,因为最终调用客户端将等待我们的请求完成。

My question is: Who / when / how does this wait for complete blocking occurs? 我的问题是:谁/何时/如何等待完全阻止?

Note: I saw the blog post There Is No Thread , and it makes sense for GUI applications, but for this server side scenario I don't get it. 注意:我看到博客文章没有线程 ,它对GUI应用程序有意义,但对于这个服务器端场景我不明白。 Really. 真。

There's some good resources on the 'net that do describe this in detail. “网上有一些很好的资源可以详细描述这一点。 I wrote an MSDN article that describes this at a high level . 我写了一篇MSDN文章,高级描述了这一点

What i do not understand is why the request is still alive and waits for an answer because in the end the calling client will wait for our request to complete. 我不明白的是为什么请求仍然存在并等待答案,因为最终调用客户端将等待我们的请求完成。

It's still alive because the ASP.NET runtime has not yet completed it. 它仍然存在,因为ASP.NET运行时尚未完成它。 Completing the request (by sending the response) is an explicit action; 完成请求(通过发送响应)是一个明确的行动; it's not like the request will complete on its own. 它不像请求会自行完成。 When ASP.NET sees that the controller action returns a Task / Task<T> , it will not complete the request until that task completes. 当ASP.NET看到控制器操作返回Task / Task<T> ,它将不会在该任务完成之前完成请求。

My question is: Who / when / how does this wait for complete blocking occurs ? 我的问题是:谁/何时/如何等待完全阻止?

Nothing is waiting. 什么都没有等待。

Think of it this way: ASP.NET has a collection of current requests that it's processing. 可以这样想:ASP.NET有一组正在处理的当前请求。 For a given request, as soon as it's complete, the response is sent out and then that request is removed from the collection. 对于给定的请求,一旦完成,就会发送响应,然后从集合中删除该请求。

The key is that it's a collection of requests, not threads. 关键是它是一组请求,而不是线程。 Each of those requests may or may not have a thread working on it at any point in time. 这些请求中的每一个在任何时间点都可能有或没有线程处理它。 Synchronous requests always have a single thread (the same thread). 同步请求总是有一个线程(同一个线程)。 Asynchronous requests may have periods when they don't have threads. 异步请求可能具有没有线程的句点。

Note: i saw this thread: http://blog.stephencleary.com/2013/11/there-is-no-thread.html and it makes sense for GUI applications but for this server side scenario I don't get it. 注意:我看到了这个帖子: http//blog.stephencleary.com/2013/11/there-is-no-thread.html它对GUI应用程序有意义,但对于这个服务器端场景,我没有得到它。

The threadless approach to I/O works exactly the same for ASP.NET apps as it does for GUI apps. 对于ASP.NET应用程序,I / O的无线方法与GUI应用程序的工作方式完全相同。

Eventually, the file write will complete, which (eventually) completes the task returned from ReadFile . 最终,文件写入将完成,最终完成从ReadFile返回的任务。 This "completing of the task" work is normally done with a thread pool thread. 这种“完成任务”的工作通常是通过线程池线程完成的。 Since the task is now complete, the Upload action will continue executing, causing that thread to enter the request context (that is, there is now a thread executing that request again). 由于任务现在已完成,因此上Upload操作将继续执行,导致该线程进入请求上下文(即,现在有一个线程再次执行该请求)。 When the Upload method is complete, then the task returned from Upload is complete, and ASP.NET writes out the response and removes the request from its collection. Upload方法完成后,上Upload返回的任务完成,ASP.NET写出响应并从其集合中删除请求。

Under the hood the compiler performs a sleight of hand and transforms your async \\ await code into a Task -based code with a callback. 在引擎盖下,编译器会执行一些操作,并将async \\ await代码转换为带有回调的基于Task的代码。 In the most simple case: 在最简单的情况下:

public async Task X()
{
    A();
    await B();
    C();
}

Gets changed into something like: 变为以下内容:

public Task X()
{
    A();
    return B().ContinueWith(()=>{ C(); })
}

So there's no magic - just a lot of Task s and callbacks. 所以没有魔法 - 只需要很多Task和回调。 For more complex code the transformations will be more complex too, but in the end the resulting code will be logically equivalent to what you wrote. 对于更复杂的代码,转换也会更复杂,但最终生成的代码在逻辑上等同于您编写的代码。 If you want, you can take one of ILSpy/Reflector/JustDecompile and see for yourself what is compiled "under the hood". 如果你愿意,你可以使用ILSpy / Reflector / JustDecompile中的一个,并亲自查看“引擎盖下”编译的内容。

ASP.NET MVC infrastructure in turn is intelligent enough to recognize if your action method is a normal one, or a Task based one, and alter its behavior in turn. 反过来,ASP.NET MVC基础设施足够智能,可以识别您的操作方法是正常的,还是基于Task的操作方法,并依次改变其行为。 Therefore the request doesn't "disappear". 因此请求不会“消失”。

One common misconception is that everything with async spawns another thread. 一个常见的误解是,所有使用async东西都会产生另一个线程。 In fact, it's mostly the opposite. 事实上,它大多相反。 At the end of the long chain of the async Task methods there is normally a method which performs some asynchronous IO operation (such as reading from disk or communicating via network), which is a magical thing performed by Windows itself. async Task方法的长链的末尾,通常有一种方法可以执行一些异步IO操作(例如从磁盘读取或通过网络进行通信),这是Windows本身执行的一项神奇的操作。 For the duration of this operation, there is no thread associated with the code at all - it's effectively halted. 在此操作期间,根本没有与代码关联的线程 - 它实际上已停止。 After the operation completes however, Windows calls back and then a thread from the thread pool is assigned to continue the execution. 但是,操作完成后,Windows将回调,然后分配来自线程池的线程以继续执行。 There's a bit of framework code involved to preserve the HttpContext of the request, but that's all. 保留请求的HttpContext有一些框架代码,但这就是全部。

The ASP.NET runtime understands what tasks are and delays sending the HTTP response until the task is done. ASP.NET运行时了解任务是什么,并延迟发送HTTP响应,直到任务完成。 In fact the Task.Result value is needed in order to even generate a response. 事实上,需要Task.Result值才能生成响应。

The runtime basically does this: 运行时基本上是这样的:

var t = Upload(...);
t.ContinueWith(_ => SendResponse(t));

So when your await is hit both your code and the runtimes code gets off the stack and "there is no thread" at that point. 因此,当您的await被命中时,您的代码和运行时代码都会从堆栈中移出,并且“此时没有线程”。 The ContinueWith callback revives the request and sends the response. ContinueWith回调会恢复请求并发送响应。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM