[Windows翻译]Async-Async。减少跨线程异步操作的烦躁感

454 阅读5分钟

原文地址:devblogs.microsoft.com/oldnewthing…

原文作者:devblogs.microsoft.com/oldnewthing…

发布时间:2019年4月30日

Windows Runtime 用 IAsyncOperation<T> 和 IAsyncAction 接口来表达异步活动的概念。前者代表一个异步完成的操作,其结果为T类型。后者代表一个没有结果的异步完成的操作,你可以把它看成 IAsyncOperation<void>。事实上,为了这次讨论的目的,我们就把它当作这样的方法。

当你调用DoSomethingAsync这样的方法时,它将返回一个IAsyncOperation接口的实例。IAsyncOperation接口的所有细节通常都被语言投影隐藏起来,不让开发者知道。如果你在C#中编写,你会看到一个Task;在JavaScript中,你会得到一个Promise。在C++/WinRT和C++/CX中,你co_await IAsyncOperation,co_await机械隐藏了细节。在C++/CX中,你也可以将IAsyncOperation转换为concurrency::task,然后用这种方式安排你的继续。

但今天,我们要看看事情是如何在掩盖之下工作的。

在原始接口层面,异步操作的工作原理是这样的。在图中,一个实心箭头代表一个调用,一个虚线箭头代表该调用的返回。

sequenceDiagram
  Client ->>+ Server: DoSomethingAsync()
  Server ->> Server: Start operation
  Server -->>- Client: IAsyncOperation
  Client ->>+ Server: put_Completed(callback)
  Server -->>- Client: void
  Client ->>+ Server: Release()
  Server ->> Server: release IAsyncOperation
  Server -->>- Client: void
  Note over Client,Server: … time passes …
  Server ->>+ Client: callback.Invoke()
  Client ->>+ Server: get_Status()
  Server -->>- Client: Completed (or Error)
  Client ->>+ Server: GetResults()
  Server -->>- Client: results
  Client -->>- Server: void
  Server ->> Server: IAsyncOperations is destroyed

当客户端调用DoSomethingAsync()方法时,该调用被发送到服务器,服务器开始操作,并返回一个表示正在进行的操作的IAsyncOperation。

客户端调用 IAsyncOperation::put_Completed 方法来指定一个回调,该回调将在操作完成时被调用,从而允许客户端在操作完成时继续执行。服务器保存这个回调并返回。

客户端释放IAsyncOperation,因为它不再需要它。操作本身让 IAsyncOperation 活着。

时间流逝,最终操作完成。

服务器调用回调来让它知道操作已经完成。客户端会收到一个对原始 IAsyncOperation 的引用,作为回调的一部分。客户端可以询问IAsyncOperation,以确定操作是否成功,如果成功,结果是什么。

最后,当回调返回时,对 IAsyncOperation 再也没有未完成的引用了,所以它自己销毁了。

你可能已经注意到,这是客户端和服务器之间的一个话痨的接口。我的意思是,看看那些箭头!

进入Async-Async。

Async-Async在客户端和服务器上都插手了做本地缓存的层。该层向客户端返回一个假的async操作,并向服务器提供一个假的客户端。

sequenceDiagram
  participant C as Client
  participant CL as Client Layer
  participant SL as Server Layer
  participant S as Server
  C ->>+ CL: DoSomethingAsync()
  CL ->> CL: create fake IAsyncOperation
  CL ->> SL: fake Client
  CL -->>- C: fake IAsyncOperation
  SL ->>+ S: Start operation
  C ->>+ CL: put_Completed(callback)
  S -->>- SL: IAsyncOperation
  CL ->> CL: save in fake IAsyncOperation
  CL ->> SL: fake Client
  CL -->>- C: void
  SL ->>+ S: put_Completed(private)
  C ->>+ CL: Release()
  S -->>- SL: void
  CL ->> CL: release fake IAsync Operation
  CL ->> SL: fake Client
  CL -->>- C: void
  SL ->>+ S: Release()
  S ->> S: release IAsync Operation
  S -->>- SL: void

... time passes ...

sequenceDiagram
  participant C as Client
  participant CL as Client Layer
  participant SL as Server Layer
  participant S as Server
  S ->>+ SL: private.Invoke()
  SL ->>+ S: get_Status()
  S -->>- SL: Completed (or Error)
  SL ->>+ S: GetResults()
  S -->>- SL: results
  SL ->> CL: status and results
  CL ->> CL: cache status and results
  CL ->>+ C: callback.Invoke()
  CL ->> SL: fake Client
  C ->>+ CL: get_Status()
  SL -->>- S: private returns
  CL -->>- C: cached status
  S ->> S: IAsyncOperation is destroyed
  C ->>+ CL: GetResults()
  CL -->>- C: cached results
  C -->>- CL: callback returns
  CL ->> CL: fake IAsync Operation is destroyed

在Async-Async中,客户端对DoSomethingAsync()的调用会在客户端创建一个假的AsyncOperation。这个假的 IAsyncOperation 向服务器发出调用以启动操作,但并不等待服务器响应请求。相反,这个假的 IAsyncOperation 会立即返回到客户端。

与之前一样,客户端调用 IAsyncOperation::put_Completed 方法来指定一个回调,该回调将在操作完成时被调用,从而允许客户端在操作完成时恢复执行。假的 IAsyncOperation 保存这个回调并返回。

客户端释放假的 IAsyncOperation,因为它不再需要它。操作本身保持了IAsyncOperation的活力。

同时,来自假的 IAsyncOperation 的请求到达服务器,在那里构造了一个假的客户端。这个假客户端要求真正的服务器开始操作,它注册了自己的私有回调,以便在操作完成时得到通知,然后它释放IAsyncOperation。

时间流逝,最终操作完成。

服务器调用回调来通知假客户端操作完成。假客户端立即检索状态和结果,并将两者传送给假的 IAsyncOperation,从而完成了一开始由假的 IAsyncOperation 启动的异步调用。

然后,假的客户端从其回调中返回,服务器端的一切工作就全部完成了。

同时,假的IAsyncOperation已经收到了操作的状态和结果,并调用客户端的回调。和之前一样,客户端调用IAsyncOperation::get_Status()方法来了解操作是否成功,它调用IAsyncOperation::GetResults()方法从假的IAsyncOperation中获取异步操作的结果。客户端从它的回调中返回,现在客户端的一切工作都已经完成了。

这个接口就不那么聊得来了。只有一个从客户端到服务器的调用(启动操作),只有一个从服务器返回客户端的调用(指示操作的状态和结果)。其余所有的调用都是本地的,因此速度很快。

从客户端的角度来看,Async-Async将异步操作变得更加异步:不仅操作本身是异步运行的,甚至操作的启动也是异步进行的。这就把控制权更快地交还给了客户端,这样它就可以做一些有成效的事情,比如说,运行其他准备好的任务。

请注意,Async-Async只有在方法调用需要被调集时才会发挥作用。如果客户端和服务器在同一个线程上,那么就不需要Async-Async,因为这些调用都已经是本地的了。

Async-Async是在Windows 10中引入的,几乎所有Windows提供的异步操作都启用了它。有一些方法不使用Async-Async,因为它们需要同步启动;UI操作就属于这一类。

你可以通过在你的方法中添加[remote_async]属性来为你自己的异步操作启用Async-Async。

runtimeclass Awesome
{
  [remote_async]
  Windows.Foundation.IAsyncAction BeAwesomeAsync();
};

虽然Async-Async的目的是对客户端透明,但还是有一些需要注意的地方。我们下次再看这些。


www.deepl.com 翻译