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

Geo*_*ica 36 c# asp.net-mvc multithreading asynchronous async-await

当我在MVC控制器上进行异步操作时,尤其是在处理I/O操作时,我并不完全理解幕后发生了什么.假设我有一个上传动作:

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

  ...
}
Run Code Online (Sandbox Code Playgroud)

据我所知,这些是发生的基本步骤:

  1. 从线程池中查看新线程并分配给处理命令请求.

  2. 当await被命中时,如果调用是I/O操作,则原始线程返回池中,控制转移到所谓的IOCP(输入输出完成端口).我不明白为什么请求仍然存在并等待答案,因为最终调用客户端将等待我们的请求完成.

我的问题是:谁/何时/如何等待完全阻止?

注意:我看到博客文章没有线程,它对GUI应用程序有意义,但对于这个服务器端场景我不明白.真.

Ste*_*ary 23

"网上有一些很好的资源可以详细描述这一点.我写了一篇MSDN文章,高级描述了这一点.

我不明白的是为什么请求仍然存在并等待答案,因为最终调用客户端将等待我们的请求完成.

它仍然存在,因为ASP.NET运行时尚未完成它.完成请求(通过发送响应)是一个明确的行动; 它不像请求会自行完成.当ASP.NET看到控制器操作返回一个Task/时Task<T>,它将不会在该任务完成之前完成请求.

我的问题是:谁/何时/如何等待完全阻止?

什么都没有等待.

可以这样想:ASP.NET有一组正在处理的当前请求.对于给定的请求,一旦完成,就会发送响应,然后从集合中删除该请求.

关键是它是一组请求,而不是线程.这些请求中的每一个在任何时间点都可能有或没有线程处理它.同步请求总是有一个线程(同一个线程).异步请求可能具有没有线程的句点.

注意:我看到了这个帖子:http://blog.stephencleary.com/2013/11/there-is-no-thread.html它对GUI应用程序有意义但是对于这个服务器端场景我不明白.

对于ASP.NET应用程序,I/O的无线方法与GUI应用程序的工作方式完全相同.

最终,文件写入将完成,最终完成从中返回的任务ReadFile.这种"完成任务"的工作通常是通过线程池线程完成的.由于任务现在已完成,Upload操作将继续执行,导致该线程进入请求上下文(即,现在有一个线程再次执行该请求).当Upload方法完成,则任务返回的Upload是完整的,和ASP.NET写出响应,并删除其收藏的要求.

  • "关键是它是请求的集合,而不是线程.每个请求可能会或可能没有任何时间点上的线程.同步请求总是有一个线程(同一个线程).异步请求可能有没有线程的时期." 清理干净吧. (3认同)

Vil*_*lx- 11

在引擎盖下,编译器会执行一些操作,并通过回调将您的async\ await代码转换为Task基于代码的代码.在最简单的情况下:

public async Task X()
{
    A();
    await B();
    C();
}
Run Code Online (Sandbox Code Playgroud)

变为以下内容:

public Task X()
{
    A();
    return B().ContinueWith(()=>{ C(); })
}
Run Code Online (Sandbox Code Playgroud)

所以没有魔法 - 只有很多Tasks和回调.对于更复杂的代码,转换也会更复杂,但最终生成的代码在逻辑上等同于您编写的代码.如果你愿意,你可以使用ILSpy/Reflector/JustDecompile中的一个,并亲自查看"引擎盖下"编译的内容.

反过来,ASP.NET MVC基础结构足够智能,可以识别您的操作方法是正常方法还是Task基于方法,并依次改变其行为.因此请求不会"消失".

一个常见的误解是,所有东西都async产生了另一个线程.事实上,它大多相反.在async Task方法的长链的末尾,通常存在执行一些异步IO操作的方法(例如从磁盘读取或通过网络进行通信),这是由Windows本身执行的神奇事物.在此操作期间,根本没有与代码关联的线程 - 它实际上已停止.但是,操作完成后,Windows将回调,然后分配来自线程池的线程以继续执行.有一些框架代码可以保留HttpContext请求,但这就是全部.


usr*_*usr 10

ASP.NET运行时了解任务是什么,并延迟发送HTTP响应,直到任务完成.实际上,Task.Result需要该值才能生成响应.

运行时基本上是这样的:

var t = Upload(...);
t.ContinueWith(_ => SendResponse(t));
Run Code Online (Sandbox Code Playgroud)

因此,当你await遇到你的代码和运行时代码从堆栈中获取并且那时"没有线程"时.该ContinueWith回调复苏的请求,并发送该响应.