本篇翻译的英文链接:https://docs.microsoft.com/en-us/dotnet/articles/standard/async-in-depth

使用.NET的基于任务的异步编程模型,可以直观地编写处理I/O密集型或是CPU计算密集型问题的异步代码。这个模型暴露了Task和Task<T>类型,以及async和await两个语言关键字给外界使用。这篇文章解释了如何使用.NET async模型,以及探究表面之下的async框架的工作原理。

Task and Task<T>

Tasks这种构造,是用来实现众所周知的"Promise Model of Concurrency"模型的。简单来说,它们"承诺"工作会在后续的某个时间点完成,允许你使用更干净的API编写代码以达成目标。

* Task表示一个不返回任何值的操作。

* Task<T>表示返回值为类型T的操作。

把Tasks看作一段异步进行的操作的抽象,而不是基于线程的抽象,是非常重要的。默认情况下,Tasks在当前线程上执行并且视情况将工作委托给OS。也可以显式调用Task.Run接口以要求Tasks在一个单独的线程上运行。

对一个task(任务),Tasks提供了API用于监控,等待和访问返回值(使用Task<T>时)。而关键字await则在语言层面上为tasks的使用提供了更高层别的抽象。

使用await允许你的应用程序或服务在运行一个task时,转让控制权给此task的调度者以更有效地工作,直至此task执行完毕。而你的代码也不需要再以回调或是事件的方式来处理task结束后的工作,Task API与语言层面的结合已经帮你做了这些。如果你使用Task<T>的话,那么await在task结束时会抽离出返回结果Task<T>中的T值。工作细节会在下面进一步解释。

你可以通过"Task-based Asynchronous Pattern(TAP) Article"来学习更多关于Tasks的知识,以及与其交互的不同途径。

Deeper Dive into Tasks for an I/O-Bound Operation

下面的小节描述了一个经典的async I/O场景。先看几个例子。

第一个例子调用一个async方法并且返回一个可能尚未完成的活动任务。

 public Task<string> GetHtmlAsync()
{
// Execution is synchronous here
var client = new HttpClient(); return client.GetStringAsync("http://www.dotnetfoundation.org");
}

第二个例子在task的控制上添加了async和await关键字的使用。

 public async Task<string> GetFirstCharactersCountAsync(string url, int count)
{
// Execution is synchronous here
var client = new HttpClient(); // Execution of GetFirstCharactersCountAsync() is yielded to the caller here
// GetStringAsync returns a Task<string>, which is *awaited*
var page = await client.GetStringAsync("http://www.dotnetfoundation.org"); // Execution resumes when the client.GetStringAsync task completes,
// becoming synchronous again. if (count > page.Length)
{
return page;
}
else
{
return page.Substring(, count);
}
}

方法GetStringAsync()会调用一系列的底层.net库(可能会调用其它的async方法)直到它通过P/Invoke方式调用到一个本地网络库。这个本地库可能会后续调用系统API(比如Linux上调用socket的write)。在本地/托管的交互边界,会创建一个task对象,并被层层向上传递,中途可能会被操作或是直接返回,最后返回给初始的caller。

在第二个例子中,GetStringAsync方法会返回一个Task<T>对象。使用await时,此方法会返回一个新创建的task对象。在这个点,控制权会移交给GetFirstCharactersCountAsync方法的caller。Task<T>的方法和属性使得callers可以监控这个task的进度,这个task会在GetFirstCharactersCountAsync方法的剩余代码执行后才真正结束。

系统API调用之后,请求正处在内核态通往OS的网络子系统的途中(比如linux内核的/net)。在这里,OS依然会异步处理这个网络请求。具体细节可能会依赖于具体平台而有不同,但最后运行时都会收到网络请求正在进行中的通知。此时,设备驱动可能正在调度处理,也有可能已经处理结束了(请求已经结束了传输--原文是the request is already out "over the wire")--但是因为这些都是异步发生的,设备驱动因而有能力立即处理其它的事情!

举个例子,在Windows中一个OS线程调用网络设备驱动并请求它通过IRP(Interrupt Request Packet)的方式处理网络操作。设备驱动收到IRP后,将请求转发给网络层,并将IRP打上"pending"(等待)的标记,然后返回到OS。因为OS线程知道IRP正在"pending",所以不需要再做什么工作并返回,以便处理其它的工作。

当请求被处理,数据通过设备驱动返回后,它通过中断通知CPU新数据的到达。至于这个中断是如何处理的,依赖于具体的OS,但是最后这份数据会通过OS传递直到它到达一个系统交互调用层。注意这些依然是异步发生的!结果被入队直至被下一个有空闲的线程调用相应的异步方法,并且将结果从已完成的任务剥离出来。

整个的处理过程,一个关键点是:在处理任务的过程中,没有线程在等待,没有浪费。虽然工作是在一定的上下文中处理的(比如,OS必须把数据传递给设备驱动并且应答中断),但没有任何线程浪费在等待数据从请求至返回的过程中。这可以大大提升系统的吞吐量,而不是把时间浪费在等待I/O调用完成上。

上述看来,似乎有很多的工作要做,但是用时钟周期来衡量时,与实际的I/O工作所花费的时间相比,其花费是相当小的。虽然不能很精确地描述,但是像这种调用的时间轴看起来可以是下面这个样子的:

0-1--------2-3

*从时间点0到时间点1,所有的事情都在被处理,直到一个异步方法将控制权移交给它的caller。

*从时间点1到时间点2是花费在I/O上的时间,没有任何CPU的开销。

*最后,从时间点2到时间点3这段时间内,是将控制权(也包括可能的返回值)交回给异步方法,并继续执行。

What does this mean for server scenario?(在服务器场合的意义)

这个模型在典型的服务器场景下工作良好。因为没有任何线程会阻塞在未完成的任务上,所以服务器的线程池可以处理更高数量的web请求。

考虑两种服务器:一种是运行async code,另一种不运行。这个例子中我们假设每台server只有5个线程有能力处理服务请求。注意这个数量很小并且工作在很普通的应用场景中。

假设两台server都同时收到6个并发请求。每个请求会导致一个I/O操作。没有async code的server必须缓存第6个请求直到5个线程处理完所有的I/O操作并给予了应答。假设在这个点进来了第20个请求,这台server的处理速度很可能开始变慢,因为请求队列已经变得太长了。

使用async方式编写的server依然会将第6个请求入队,但是因为它使用async和await关键字,每个使用它的线程在I/O工作开始时都会被释放,而不是等I/O完成才释放。当第20个请求到来时,接收进来的请求的队列要小得多,server也不会出现变慢的情况。

虽然这只是个人为的例子,但是真实世界中的工作方式与它是很相像的。事实上,当server以async和await方式处理大量的请求时,你可以认为在处理每个请求时,它是以单线程的方式来工作的。

What does this mean for client scenario?(在客户端场合的意义)

对于客户端应用来说,使用async和await带来的最大好处莫过于在响应上的提升。尽管你可以通过手动开启额外的线程来提升应用的响应能力,但与使用async和await相比,开启新线程的代价是很大的。尤其对于移动游戏这种来说,尽可能减少对UI线程的压力是非常关键的。

更重要的是,因为I/O密集型的工作基本不花费CPU任何时间,占用一个完整的CPU线程而几乎不能做什么有用的工作是对资源的严重浪费。

另外,使用async方法将工作分派给UI线程是非常简单的,而且不需要做任何额外多余的工作(比如调用一个线程安全的委托等)。

Deeper Dive into Task and Task for a CPU-Bound Operation

    使用async编写CPU密集型问题的代码与编写I/O密集型的代码有一点不同。因为工作是在CPU上完成的,在执行计算时是无法将CPU线程释放出来的。async和await提供了一种清晰的与后台线程的交互方式,同时又能使async方法的调用者保持响应。注意其并不为共享数据提供任何保护。如果你正在使用共享数据,你依然需要应用合适的数据同步策略。

下面是CPU密集型的async方法演示:

 public async Task<int> CalculateResult(InputData data)
{
// This queues up the work on the threadpool.
var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data)); // Note that at this point, you can do some other work concurrently,
// as CalculateResult() is still executing! // Execution of CalculateResult is yielded here!
var result = await expensiveResultTask; return result;
}

CalculateResult方法是在调用者的线程上执行的。当其调用Task.Run方法,它将代价昂贵的CPU操作DoExpensiveCalculation向线程池中入队,并返回一个Task<int>句柄。DoExpensiveCalculation()最终会在下一个可用线程上并发执行,可能在另一个单独的CPU核上。当DoExpensiveCalculation()在另一个线程上执行时,是可以并发地做其它工作的,因为调用CalculateResult()的线程还在跑。

一旦执行到await时,CalculateResult()的执行便会移交给它的调用者,允许DoExpensiveCalculation()计算结果时,当前线程依然可以执行其它的工作。一旦计算结束,结果会被入队,等待在主线程上运行。最后,主线程会返回到DoExpensiveCalculation的执行点,取得结果,并继续向下运行。

Why does async help here?

    当你需要处理CPU密集型的工作,但是又需要响应能力时,async和await是最佳实践。使用async编写CPU密集型工作时,有几个模式可供参考。需要注意的是,使用async不是完全没有开销的,不建议在紧凑的循环中使用。至于如何使用这个新能力来编写代码,那就看你自己的了。

async-await系列翻译(一)的更多相关文章

  1. 进阶篇:以IL为剑,直指async/await

    接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理. 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的 ...

  2. Python PEP 492 中文翻译——协程与async/await语法

    原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...

  3. [翻译] Python 3.5中async/await的工作机制

    Python 3.5中async/await的工作机制 多处翻译出于自己理解,如有疑惑请参考原文 原文链接 身为Python核心开发组的成员,我对于这门语言的各种细节充满好奇.尽管我很清楚自己不可能对 ...

  4. [原创.数据可视化系列之十二]使用 nodejs通过async await建立同步数据抓取

    做数据分析和可视化工作,最重要的一点就是数据抓取工作,之前使用Java和python都做过简单的数据抓取,感觉用的很不顺手. 后来用nodejs发现非常不错,通过js就可以进行数据抓取工作,类似jqu ...

  5. MVC+Spring.NET+NHibernate .NET SSH框架整合 C# 委托异步 和 async /await 两种实现的异步 如何消除点击按钮时周围出现的白线? Linq中 AsQueryable(), AsEnumerable()和ToList()的区别和用法

    MVC+Spring.NET+NHibernate .NET SSH框架整合   在JAVA中,SSH框架可谓是无人不晓,就和.NET中的MVC框架一样普及.作为一个初学者,可以感受到.NET出了MV ...

  6. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  7. JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧

    原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...

  8. 【译】Async/Await(五)—— Executors and Wakers

    原文标题:Async/Await 原文链接:https://os.phil-opp.com/async-await/#multitasking 公众号: Rust 碎碎念 翻译 by: Praying ...

  9. async & await 的前世今生(Updated)

    async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...

  10. [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程

    怎样使用 async & await 一步步将同步代码转换为异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6079707.html  ...

随机推荐

  1. Python入门---易错已错易混淆----知识点

    1.not 1 or 0 and 1 or 3 and 4 or 5 and 6 or 7 and 8 and 9 结果会输出啥? 根据优先级:(not 1) or (0 and 1) or (3 a ...

  2. android studio settings

    安装 Android Studio下载地址   http://www.android-studio.org/ 1.配置JDK 2.安装 Android Studio (带SDK) 3.配置 一.Set ...

  3. JavaSE的static、final、abstract修饰符

    static :静态常量,静态方法,静态代码块     静态变量:  静态变量属于类的,使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问.           注意: ...

  4. poi 读取excel row.getCell() 为null

    ##### getCell()为null 科目 余额 1 利息 1000 2 60 3 现金 10000 表格第一个单元为空时getCell()为null,直接使用会出现空指针异常

  5. 使用Swagger生成Spring Boot REST客户端(支持Feign)(待实践)

    如果项目上使用了Swagger做RESTful的文档,那么也可以通过Swagger提供的代码生成器生成客户端代码,同时支持Feign客户端. 但是经过测试,生成Feign代码和REST客户端有些臃肿. ...

  6. MAPZONE GIS SDK接入Openlayers3之五——图形编辑工具

    图形编辑工具提供对要素图形进行增.删.改的功能,具体包括以下几种工具类型: 浏览工具 选择工具 创建要素工具 删除命令 分割工具 合并命令 节点编辑工具 修边工具 撤销命令 重做命令 工具的实现基本上 ...

  7. “ORA-01747: user.table.column, table.column 或列说明无效” 的解决方案

    此问题的原因是因为表的列名称使用了Oracle声明的关键字,列名起的不好引起的. 如果列很多,又不好确定是哪个列名使用了关键字,以下建议可供参考: select * from v$reserved_w ...

  8. flask结合令牌桶算法实现上传和下载速度限制

    限流.限速: 1.针对flask的单个路由进行限流,主要场景是上传文件和下载文件的场景 2.针对整个应用进行限流,方法:利用nginx网关做限流 本文针对第一中情况,利用令牌桶算法实现: 这个方法:h ...

  9. JQuery插件ajaxFileUpload 异步上传文件

    一.先对ajaxFileUpload插件的语法参数进行讲解 原理:ajaxfileupload是通过监听iframe的onload方法来实现, 当从服务端处理完成后,就触发iframe的onload事 ...

  10. poj1703 Find them,Catch them 【并查集】

    做过一些的带权并查集,再来做所谓的"种类并查集",发现好像就顿悟了. 种类并查集与带权并查集实质上的区别并不大. 关键的区别就是种类并查集仅仅是带权并查集再弄个%取余操作而已.然后 ...