zoukankan      html  css  js  c++  java
  • async-await系列翻译(一)

        本篇翻译的英文链接: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方法并且返回一个可能尚未完成的活动任务。

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

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

     1 public async Task<string> GetFirstCharactersCountAsync(string url, int count)
     2 {
     3     // Execution is synchronous here
     4     var client = new HttpClient();
     5 
     6     // Execution of GetFirstCharactersCountAsync() is yielded to the caller here
     7     // GetStringAsync returns a Task<string>, which is *awaited*
     8     var page = await client.GetStringAsync("http://www.dotnetfoundation.org");
     9 
    10     // Execution resumes when the client.GetStringAsync task completes,
    11     // becoming synchronous again.
    12 
    13     if (count > page.Length)
    14     {
    15         return page;
    16     }
    17     else
    18     {
    19         return page.Substring(0, count);
    20     }
    21 }

        方法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方法演示:

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

    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不是完全没有开销的,不建议在紧凑的循环中使用。至于如何使用这个新能力来编写代码,那就看你自己的了。

  • 相关阅读:
    通用的web系统数据导出功能设计实现(导出excel2003/2007 word pdf zip等)
    DALSA Coreco
    环境变量之执行文件路径的变量PATH
    命令与文件的查询
    软件开发工具GCC
    权限与命令之间的关系
    Linux防火墙
    网络管理
    分区及格式化
    VMware Workstation的网络连接方式:NAT、桥接和Host Only
  • 原文地址:https://www.cnblogs.com/Jackie-Snow/p/6432179.html
Copyright © 2011-2022 走看看