async-await 线程分析
这里没有线程
原文地址:https://blog.stephencleary.com/2013/11/there-is-no-thread.html
前言
我是在看 C#8.0 新特性异步流时在评论里看到这篇文章的,阅读之后发现这篇文章干货满满,作者解释的非常清晰,里面的本质分析内容在《CLR via C#》一书中也有讲到。更加加深了我的印象。遂在这里翻译过来,以便加深自己的理解
正文
一个本质的事实就是纯粹的异步是不会产生线程的
反对这个事实的人有很多。“不”,他们喊道:“如果我正在等待一个操作,那么这个线程就必须在执行等待!它可能是线程池线程。或者是一个操作系统(OS)线程,又或是其他设备驱动程序...”
无需理会他们。如果一个异步操作是纯粹的(pure),那么就不会有线程的。
那些持怀疑态度的人。那我们就迎合他们罢。
我们将跟踪一个异步操作一直到硬件,特别是 .net 部分和硬件部分。我们必须通过省略一些中间的细节来简化描述,但是我们不能偏离事实真相。
通常写一个异步操作(文件,网络流,USB 接口等等)。代码如下:
private async void Button_Click(object sender, RoutedEventArgs s)
{
byte[] data = ...
await myDevice.WriteAsync(data, 0, data.Length);
}
我们已经知道在 await 的时候 UI 线程是不会阻塞的。那么问题来了:这里有没有是其他线程在阻塞期间牺牲自己以至于让 UI 线程存活呢?
那让我们继续往下深究
第一步:库(比如进入到 BCL 库源代码)。我们假使 WriteAsync 是通过 [http://msdn.microsoft.com/en-us/library/system.threading.overlapped.aspx](P/Invoke 在 .net 异步的标准实现) 来实现的,它是基于overllapped I/O 的。所有它在一个设备驱动程序的句柄上开始一个 Win32 overlapped 操作。
OVERLAPPED 是一个包含了用于异步输入输出的信息的结构体;详细解释移步 https://en.wikipedia.org/wiki/Overlapped_I/O
操作系统然后就会转向设备驱动程序并开始请求一个写操作。它首先会构造一个表示写请求的对象;它被称为 I/O Request Packet(IRP)。
设备驱动程序接受到 IRP 之后并向设备提交一个写出数据的命令。如果这个设备支持直接内存访问(DMA 全称是 Direct Memory Access),这能够像写到缓冲区到寄存器一样简单。这就是设备驱动程序所做的一切;它把 IRP 标记为 "pending" (挂起) 并返回给操作系统。

本质核心就在这里:设备驱动程序正在处理 IRP 时是不会允许阻塞的。也就是说如果 IRP 不能马上完成,那么它必须要异步处理。甚至是同步 API 也是如此!在设备驱动程序级别,所有的请求(重要的)都是异步的。
这里引用了 Tomes 的知识,“无论I/O请求的类型如何,在内部,代表应用程序向驱动程序发出的I/O操作都是异步执行的”
在 IRP 挂起的时候,OS 返回库,库返回了一个未完成的任务给按钮点击事件,并暂停了 async 方法, UI 线程继续执行。
我们跟着请求继续往下走,现在到达了设备的物理层
现在写操作正在进行。那么有多少线程正处理它呢?
没有。
这里没有设备驱动程序线程、OS 线程、库(BCL)线程或者是线程池线程操作写操作。这里没有线程。
现在我们来跟着从来自内核的相应回到最初的世界。
在开始写请求之后的一段时间,设备完成了写操作。它会以中断的方式来通知 CPU。
设备驱动程序的中断服务程序(ISR(Interrupt Service Routine) )响应中断。这个中断是 CPU 级别的事件,无论哪个线程正在运行都会临时的抢占 CPU 的控制权。你可以认为 ISR 是在“借”当前正在运行的线程,但是我更倾向于 ISR 运行时的级别非常低,以至于不存在“线程”的概念。可以这么说,它们在所有线程之下进来的。
不管怎样,ISR 正确写完了,完了它会通知设备程序 “谢谢你的中断” 并且进入 DPC(Deffered Procedure Call) 队列(延迟过程调用)
当 CPU 被中断干扰时,它将会到达 DPCs。DPCs 也会执行在一个很低的级别以至于说它是一个线程是不正确的;就像 ISRs,DPCs 直接在 CPU 上运行,在线程系统之下。
PDC 接受代表写请求的 IRP 并且标记为 “已完成”。然而,这个“完成”状态只存在于 OS 级别;进程有它自己的内存空间,它必须被通知。所以 OS 会入队列一个特殊内核模式异步过程调用(APC)到拥有自己句柄的线程。
由于 BCL 库使用了标准的 P/Invoke overlapped I/O 系统,它已经在 I/O Completion Port(IOCP)注册句柄,它是线程池的一部分。所以借用 I/O 线程池线程来执行 APC,它会通知这些任务已经完成了。
这个任务已经捕捉了 UI 上下文,所以它不会直接在线程池线程上恢复异步方法。而是它将该方法的延续排队到 UI 上下文中,并且 UI 线程将恢复执行那个方法。
所以我们看到,正当一个请求处理时这里是没有线程的。当请求完成时,一些线程被借过去或者是被短暂的排队。这项工作通常在 1 毫秒左右(例如 APC 运行在线程池线程)或 1 微妙左右(例如 ISR)。但是这里没有线程是阻塞的,仅仅只是等待请求完成。

现在,我们遵循的路径是标准路径,这是如此清晰简单。这里有无数的变量,但是核心是不变的。
所以说 “这里必须有一个线程是在处理异步操作” 是不正确的。
释怀吧,不要尝试找到异步线程——这是不可能的。而是你应该去了解真相:
没有线程
该篇文章的评论也是很精彩的,特别是讨论将异步操作当作消息处理那部分讨论,建议也花时间看下
本文同步至:https://github.com/MarsonShine/MarsonShine.github.io/blob/master/mardown/async/There-IS-NO-Thread.md
async-await 线程分析的更多相关文章
- 进阶篇:以IL为剑,直指async/await
接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理. 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的 ...
- 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读
记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...
- Thread,ThreadPool,Task, 到async await 的基本使用方法和理解
很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧. ...
- 浅谈C#中的 async await 以及对线程相关知识的复习
C#5.0以后新增了一个语法糖,那就是异步方法async await,之前对线程,进程方面的知识有过较为深入的学习,大概知道这个概念,我的项目中实际用到C#异步编程的场景比较少,就算要用到一般也感觉T ...
- C#线程学习笔记十:async & await入门三
一.Task.Yield Task.Yield简单来说就是创建时就已经完成的Task,或者说执行时间为0的Task,或者说是空任务,也就是在创建时就将Task的IsCompeted值设置为0. 我们知 ...
- C#线程学习笔记九:async & await入门二
一.异步方法返回类型 只能返回3种类型(void.Task和Task<T>). 1.1.void返回类型:调用方法执行异步方法,但又不需要做进一步的交互. class Program { ...
- C#线程学习笔记八:async & await入门一
一.涉及内容 async & await是C# 5.0引入的,控制台输出所使用的$符号(拼接字符串)是C# 6.0引入的,其功能类似于string.Format()方法. 二.多线程.异步.同 ...
- 如何避免让线程摸鱼,请用异步技术 async await 拿捏他~
发现问题 你点了外卖后,会一直不做其它事情,一直等外卖的到来么? 当然不会拉! 我们来看看代码世界的: public void Query(){ // 当前线程 向 数据库服务器 发起查询命令 // ...
- [C#] 走进异步编程的世界 - 开始接触 async/await
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- 实际案例:在现有代码中通过async/await实现并行
一项新技术或者一个新特性,只有你用它解决实际问题后,才能真正体会到它的魅力,真正理解它.也期待大家能够多分享解一些解决实际问题的内容. 在我们遭遇“黑色30秒”问题的过程中,切身体会到了异步的巨大作用 ...
随机推荐
- 央行下属的上海资信网络金融征信系统(NFCS)签约机构数量突破800家
央行下属的上海资信网络金融征信系统(NFCS)签约机构数量突破800家 ——已接入NFCS的P2P网贷机构将优先 接入央行征信系统 P2P网贷行业的健康发展依赖于信用体系的支撑和保障,加快完善适应 ...
- Vue打包文件放在服务器,浏览器存在缓存问题的解决
在入口文件index.html添加 <meta http-equiv="pragram" content="no-cache"> <meta ...
- Python--day38---进程间通信--初识队列(multiprocess.Queue)之生产者,消费者模型
1,生产者消费者模型.py import random import time from multiprocessing import Queue, Process def producer(name ...
- 代码片段 Powershell修改桌面壁纸
其实只不过是利用了win32函数 function Set-Wallpaper($image){ $source = @" using System; using System.Runtim ...
- linux 字符设备注册
如我们提过的, 内核在内部使用类型 struct cdev 的结构来代表字符设备. 在内核调用你 的设备操作前, 你编写分配并注册一个或几个这些结构. [11] 11为此, 你的代码应当包含 < ...
- H3C设置时间--用户视图下
<H3C>clock datetime ? TIME Specify the time (HH:MM:SS) <H3C>clock datetime 19:29:00 ? ...
- Common Logging包装设计
类设计 LogFactory根据当前环境加载具体的Log实现: 1.从缓存中加载LogFactory 2.从系统属性org.apache.commons.logging.LogFactory 中加载L ...
- 移动端H5多页开发拍门砖经验
两年前刚接触移动端开发,刚开始比较疑惑,每次遇到问题都是到社区里提问或者吸取前辈的经验分享,感谢热衷于分享的开发者为前端社区带来欣欣向上的生命力.本文结合先前写的文章和开发经验分享给大家,希望也能帮助 ...
- JQ绑定事件的叠加和解决,index()方法的坑
JQ绑定事件的叠加和解决,index()方法的坑 前言 在做过几个不大不小的项目后,发现技术这种东西,必须要多多实践,才能发现各种问题,理论的知识掌握的再好终究是纸上谈兵. 因此目前感觉有两点是必须要 ...
- 【2016常州一中夏令营Day1】
Problem 1. suffix给定一个单词,如果该单词以 er. ly 或者 ing 后缀结尾,则删除该后缀(题目保证删除后缀后的单词长度不为 0),否则不进行任何操作.Input输入一行,包含一 ...