本篇翻译的英文链接: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. vba功能语句

    VBA语句集(第1辑) 定制模块行为(1) Option Explicit '强制对模块内所有变量进行声明Option Private Module '标记模块为私有,仅对同一工程中其它模块有用,在宏 ...

  2. 转载免费的SSL证书

    目前我知道的有2种方式进行免费的SSL证书的获取 第一种:腾讯云申请 第二种:Let's Encrypt (国外在) 我一直使用第一种,还可以,有效期1年. 以下转载第二种: 实战申请Let's En ...

  3. C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案

    本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区. 文章目录 C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf ...

  4. DFS与BFS对比

    前面已经说过了深搜和广搜了,是不是有点还不是很好的分清他们?(其实分不分的请都没大有关系) 下面我们来看一看广搜与深搜的区别吧. 算法步骤上的区别 深度优先遍历图算法步骤: 1.访问顶点v 2,.依次 ...

  5. JQuery select 编程时选中原有的值

    js 此为核心代码 $(document).ready(function(){ $("#carTypeId").attr("value",'${carInfo. ...

  6. 深入GCD(五):资源竞争

    概述我将分四步来带大家研究研究程序的并发计算.第一步是基本的串行程序,然后使用GCD把它并行计算化.如果你想顺着步骤来尝试这些程序的话,可以下载源码.注意,别运行imagegcd2.m,这是个反面教材 ...

  7. AppCompatActivity

    刚开始看HelloWorld的目录结构然后就发现Android Studio中的是 import android support.v7.app.AppcompatActivity; public cl ...

  8. 应用程序中的server错误,没有名称为“ServiceBehavior”的服务行为

    应用程序中的server错误,没有名称为"ServiceBehavior"的服务行为         今天在阅读"创建和使用Web服务"的相关内容,在浏览器中查 ...

  9. 【c++】面向对象程序设计之关于继承

    面向对象程序设计的核心思想是数据抽象(类的接口与实现分离).继承和动态绑定 基类 虚函数:基类希望派生类各自定义适合自身的版本的函数 在c++中,当我们使用基类的引用或指针调用虚函数时将发生动态绑定. ...

  10. oracle死锁解决经常用法(屡试不爽)

    --1.查询被锁的情况 select object_name,machine,s.sid,s.serial# from v$locked_object l,dba_objects o ,v$sessi ...