原文地址: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的目的是对客户端透明,但还是有一些需要注意的地方。我们下次再看这些。