Async/await

在计算机编程中,async/await模式是一种存在于许多编程语言中的语法特性。这种模式使得异步非阻塞函数逻辑可以用一种类似于同步函数的方式进行构造。在语义上,它与协程的概念相关,且通常也使用类似的技术实现。该模式大都是为了让程序能够在等待一个异步的长耗时操作完成的同时,也可以正常执行代码,它通常表现为Promises或者类似的形式。

这一特性出现在C# 5.0、C++20、 Python 3.5、 F#、 Hack、 Julia、 Dart、 Kotlin 1.1、 Rust 1.39、 Nim 0.9.4、 JavaScript ES2017、 Swift 5.5 和Zig中。对于Scala则出现在一些beta版本、实验版本的插件和特定的一些实现中。

历史
F# 2.0(2007)中添加了含await点的异步逻辑。这对于后来添加到C#中的async/await机制有所启发。

微软在2011年的Async CTP(Community Technology Preview,社区技术预览版)C#中首次添加了async/await,随后在2012年的C# 5中正式发布了这一机制。

Python 3.5(2015)中支持了async/await,加入两个新的关键字async和await。

TypeScript 1.7(2015)中支持了async/await。

JavaScript在2017年支持了async/await,作为ES2017标准的一部分。

Rust 1.39.0(2019)中支持了async/await,加入一个关键字async和一个惰性求值await。

C++20(2020)中支持了async/await,加入三个新的关键字co_return、co_await和co_yield。

Swift 5.5(2021)中支持了async/await,加入三个新的关键字async、await和actor,其中actor来自于同时期发布的对演员模型的一种具体的实现。这一模型中直接用到了async/await

一个C#的例子
下面的函数用于从一个URI上下载数据,然后返回这个数据的长度,其中就是用了async/await模式。
public async Task FindPageSizeAsync(Uri uri)
{
var client = new HttpClient();
byte[] data = await client.GetByteArrayAsync(uri);
return data.Length;
}
为了便于理解,我们将上面的这一函数称为主函数。注意下文中的Promise是一个概念上统称,而不一定是具体存在的一种对象。

当函数被async关键字标记时,它会被编译器认为是异步的。这样的函数中可能会有若干个await表达式,它们可将结果绑定到Promise对象上。主函数就是一个典型的异步函数。

主函数的返回类型 [https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-7.0 Task](其中T为泛型)是C#对Promise概念的一种实现,在代码中Task表明该Promise对应的实际结果是int类型的。

当主函数被执行时,首先一个新的HttpClient实例会被赋给client。

接下来await后紧跟的一句表达式执行了client上的异步方法GetByteArrayAsync(uri)异步函数的返回值通常包括void、Task 、Task<T>、返回void的异步函数通常应该是事件监听器,对于一般的函数则应该返回Task对象,因为它能够提供更加直观的异常处理。

要在函数中使用await,必须在函数声明前加上async关键字。当需要函数返回Task类型的值时,函数声明前要加上async关键字,同时应当返回T或兼容的类型,而非Task本身;随后编译器就会将返回的T类型包装为Task泛型。不过,当非异步函数(没有使用async声明的函数)返回Task时,其值也可以被await。

下列函数代码将使用await从URL下载数据。该函数的逻辑用await实现了同时触发多个任务,无需等待其完成,这使得下一个任务无需在上一个任务完成之后才触发(这是同步的逻辑)。
public async Task SumPageSizesAsync(IEnumerable uris)
{
var client = new HttpClient();
int total = 0;
var loadUriTasks = new List>();

foreach (var uri in uris)
{
var loadUriTask = client.GetByteArrayAsync(uri);
loadUriTasks.Add(loadUriTask );
}

foreach (var loadUriTask in loadUriTasks)
{
statusText.Text = $"已找到 {total} 个字节...";
var resourceAsBytes = await loadUriTask;
total += resourceAsBytes.Length;
}

statusText.Text = $"共找到 {total} 个字节。";

return total;
}

在Scala中的使用
Scala中有一个实验性的拓展Scala-async可以实现async/await模式。它提供了一个名为await的特殊函数。

许多JavaScript库提供了可返回Promise对象的函数,它们都可以被await——只要符合JavaScript中的Promise规范。JQuery中函数返回的Promise在3.0版本以后才达到了Promises/A+兼容度。

下面是一个使用例:

async function createNewDoc() {
let response = await db.post({}); // post a new doc
return db.get(response.id); // find by id
}

async function main() {
try {
let doc = await createNewDoc();
console.log(doc);
} catch (err) {
console.log(err);
}
}
main();
Node.js 8 中包含的一样工具可将标准库中利用回调模式编写的函数当作Promise来使用。

在C++中的使用
C++ 20中正式支持了await(在C++中是co_await)。GCC、MSVC编译器支持async/await模式,包括协程以及相关的关键字例如co_await。Clang对此有部分支持。

值得注意的是std::promise和std::future虽然看起来像是可以被await求值的对象,但是实际上它们并没有实现任何类似于从协程中返回的值或者可被await的对象的相关属性;要使返回对象可以被await求值,必须在返回的对象类型上实现一系列的公共成员函数,例如await_ready、await_suspend和await_resume等。具体细节可以查看相关的参考。

#include
#include "CustomAwaitableTask.h"

using namespace std;

CustomAwaitableTask add(int a, int b)
{
int c = a + b;
co_return c;
}

CustomAwaitableTask test()
{
int ret = co_await add(1, 2);
cout

在C语言中的使用
C语言没有对await/async的官方支援。

某些协程库(例如[https://github.com/xhawk18/s_task s_task] )通过宏定义的方式,
实现了和其他语言类似的await/async的强制性语义要求,即:

  1. 必须在async标注的函数内,才能调用await;
  2. 等待一个标注为aysnc的函数,调用该函数时需要加上await;

#include
#include "s_task.h"

//定义协程任务需要的栈空间
int g_stack_main[64 * 1024 / sizeof(int)];
int g_stack0[64 * 1024 / sizeof(int)];
int g_stack1[64 * 1024 / sizeof(int)];

void sub_task(__async__, void* arg) {
int i;
int n = (int)(size_t)arg;
for (i = 0; i

参考文献

评论 (0)

  • 还没有评论,来抢沙发吧。