翻译 异步I/O不会建立新的线程

异步I/O不会建立新的线程

本文翻译自 Stephen Cleary 的 [There is No Thread] 原文地址 https://blog.stephencleary.com/2013/11/there-is-no-thread.htmlhtml

这是异步编程最基本的事实 : 异步I/O不会建立新的线程编程

反对这个事实的人不少,他们对此的见解是 "若是我await一个操做,那么必定会有一个线程正在等待,它多是一个线程池的线程,或者是操做系统线程,或者是某个设备的驱动程序"c#

不用关注这些见解, 只须要记住若是异步操做是纯粹的(方法全是async),那么就不会产生新的线程api

不过这么一个简单的结论显然并不可以让他们信服,下面就看看异步到底发生了什么网络

让咱们跟踪一个异步操做直到硬件层面,注:Net部分和设备驱动部分将被简化描述 (由于细节太多了)app

下面是一个通用的写入操做 (例如 文件、网络、USB面包机等等)异步

private async void Button_Click(object sender, RoutedEventArgs e)
{
  byte[] data = ...
  await myDevice.WriteAsync(data, 0, data.Length);
}

咱们已经知道在await的时候UI线程不会被阻塞,那么问题来了: 是不是UI线程建立了另一个线程杀了祭天换来了本身的自由....async

关于UI线程是否犯下这一恶行,让咱们深刻推断一下分布式

首先 : 看看类库 (BCL的代码) 假设 WriteAsync 是使用.Net中标准的异步方式平台调用(P/Invoke)I/O系统,这是一个基于异步方式的I/O,因此这会在设备的底层上启动一个win32异步方式I/O操做句柄异步编程

而后操做系统会告诉设备驱动程序进行写操做,代码实现是构造出表示写请求的对象,这个对象称为I/O请求包(IRP)

设备驱动程序接收到IRP后会向设备发出一个命令来写出数据,若是这个设备支持直接内存存取技术(DMA),只需将缓冲区地址写入设备寄存器便可,设备驱动程序能作到将IRP标记为 "pending" 后交把控制权交还操做系统

在这里发现了真相的核心所在 : 在处理IRP时,设备驱动是不容许阻塞的。这意味着若是IRP不能当即完成,那么它必须异步处理,即便对于同步的api也是如此!在设备驱动级别,全部(有意义)的请求都是异步的

引用书籍里的知识 "不管是什么类型的I/O请求,应用程序向驱动程序发出的I/O操做都是异步执行的"

在IRP为 "pending" 时,操做系统返回到类库,类库将未完成的任务返回到应用程序的按钮单击事件方法,该方法挂起async方法,UI线程继续执行

咱们已经跟踪请求到系统的尽头 直到物理设备

如今写入操做正在飞快的进行中,有多少个线程正在处理它?

答案是没有

没有设备驱动程序线程、操做系统线程、BCL线程或者写在处理写操做的线程池线程(🙂) 这里没有建立任何新的线程

如今,让咱们跟踪内核守护进程的响应回到正常的人类世界(被大神虐了)

写入请求开始后的某个时间,设备完成了写入操做。它会举手报告中断一下CPU

设备驱动程序的中断服务程序 (ISR) 对中断操做出响应,中断是CPU级事件,它会临时从正在运行的线程中夺取CPU的控制权。能够将ISR看做 "借用" 当前正在运行的线程,但我(原做者)更倾向于认为ISR很是低的级别上运行,以致于 "线程" 的概念还不存在 - 所以能够这么说,它们在线程的下方出现(在线程以前就有了ISR)

不管如何,ISR是正确的,所以它所作的就是告诉设备“谢谢您的中断”,并对延迟过程调用(DPC)进行排队

当CPU成功被中断吸引注意力时,它会到DPCs(注: Distributed Process Control System 分布式处理控制系统),DPCs也是在一很是低的级别中运行(很是底层),说是它是"线程"并不彻底正确,与ISR同样,DPCs直接在CPU上运行,在线程系统的"下方"

DPC会把表示写请求的IRP标记为 "complete" ,可是 "completion"状态仅存在于操做系统级别,进程有本身的内存空间因此咱们必须通知到它,所以操做系统将一个特殊内核态的异步过程调用(Asyncroneus Procedure Call 简称 APC) 排队到拥有句柄的线程

因为类库/BCL使用了标准的异步方式平台调用(P/Invoke)系统,因此它已经注册了带有I/O输出完成端口(ICOP 注:I/Q Completion Port)的句柄,IOCP是线程池的一部分,所以简单的借用I/O线程池线程来执行APC,APC通知Task已经完成

该Task已捕获UI上下文,所以它不会直接在线程池线程上恢复异步方法,相反,它将该方法排队到UI上下文中,UI线程会恢复执行该方法

所以,咱们看到在请求在被处理时没有建立新的线程,当请求完成后,各类线程被"借用",或者有一些处理被简单的排队等候,这个处理一般是在毫秒级时间内完成 (例如 在线程池中运行的APC),甚至到微秒级(例如 ISR),可是没有线程被阻塞,只是等待该请求完成

咱们在上面的样例中使用了被简化后最标准的方式,在实际开发中会比这个样例复杂不少,可是核心的事实是同样的

"必须有一个处理异步操做的线程" 并非事实

打破常规,不要识图去找到这个 "异步线程",这是不可能的。相反,要去寻找真相:

没有线程

相关文章
相关标签/搜索