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中正式发布了这一机制。
Haskell的主要开发者于2012年开发了async包。
Python 3.5（2015）中支持了async/await，加入两个新的关键字codice_1和codice_2。
TypeScript 1.7（2015）中支持了async/await。
JavaScript在2017年支持了async/await，作为ES2017标准的一部分。
Rust 1.39.0（2019）中支持了async/await，加入一个关键字codice_1和一个惰性求值codice_2。
C++20（2020）中支持了async/await，加入三个新的关键字codice_5、codice_6和codice_7。
Swift 5.5（2021）中支持了async/await，加入三个新的关键字codice_1、codice_2和codice_10，其中codice_10来自于同时期发布的对演员模型的一种具体的实现。这一模型中直接用到了async/await
一个C#的例子.
下面的函数用于从一个URI上下载数据，然后返回这个数据的长度，其中就是用了async/await模式。
public async Task&lt;int&gt; FindPageSizeAsync(Uri uri) 
 var client = new HttpClient();
 byte[] data = await client.GetByteArrayAsync(uri);
 return data.Length;
为了便于理解，我们将上面的这一函数称为主函数。注意下文中的Promise是一个概念上统称，而不一定是具体存在的一种对象。
异步函数内部可以根据需要使用多个await语句，每一个语句都会以相同的方式进行处理（实际上只有第一个await语句会返回Promise，其余的await都用机制类似的内部回调函数实现）。对于返回的Promise对象，算法中亦可以对其直接进行处理（例如先保存起来），从而实现先执行其它任务（包括触发新的异步Task），等到需要相关结果的时候才使用await语句处理Promise对象，拿到结果。
除了直接await之外，也有一些可以批量处理Promise对象的函数，比如C#中的codice_26函数，它会返回一个codice_27（无值Task，可以理解为codice_28）。这个被返回的codice_27会在codice_26方法参数中提供的所有的Promise被resolve以后resolve。还有一些Promise返回类型支持通常async/await模式不会用到的一些方法，例如对Promise设置多个结果的回调函数、监听长耗时Task的执行进程等。
在C#和许多其它语言中，async/await模式并不是运行时的核心组成部分，而实际上会在编译的时候使用Lambda表达式或者续体来实现。例如上面的C#代码很可能会被编译器先转换成下面的代码，然后才被转换成字节码。
public Task&lt;int&gt; FindPageSizeAsync(Uri uri) 
 var client = new HttpClient();
 Task&lt;byte[]&gt; dataTask = client.GetByteArrayAsync(uri);
 Task&lt;int&gt; afterDataTask = dataTask.ContinueWith((originalTask) =&gt; {
 return originalTask.Result.Length;
 return afterDataTask;
也正因此，如果某个函数需要返回一个Promise对象，但是其本身并不需要进行任何的await求值，那么它并不需要在函数声明前面加上async来让自己成为一个异步函数，而是可以直接返回一个Promise对象。例如，使用C#的codice_31方法来返回一个马上resolve的codice_27，或者直接如同地铁换乘一样将其它函数所提供的codice_27直接原样返回。
不过对于这项功能必须要注意的一项是，尽管在异步函数内部的逻辑长得很像同步的形式，这些逻辑实际上是非阻塞的，甚至可能是多线程的。所以在await语句等待Promise被resolve的时候，可能会发生许多侵入性的事件。比如下面的代码，如果没有await，那么将始终执行成功，但是如果使用了async/await模式，就可能会出现codice_34发生改变（被其它逻辑）的情况。
var a = state.a;
var client = new HttpClient(); // 与a无关的语句
var data = await client.GetByteArrayAsync(uri); // 与a无关的语句
Debug.Assert(a == state.a); // ★这个语句可能会出现错误，因为state.a可能在await的过程中被其它逻辑篡改。
return data.Length;
在F#中的使用.
F#中的异步逻辑的具体实现是计算表达式。具体使用时并不需要加上特殊的标识符，例如codice_1。在代码逻辑中，使用一个感叹号（codice_36）来开始异步操作。
从URL下载数据的异步函数逻辑如下：
let asyncSumPageSizes (uris: #seq&lt;Uri&gt;) : Async&lt;int&gt; = async {
 use httpClient = new HttpClient()
 let! pages = 
 uris
 |&gt; Seq.map(httpClient.GetStringAsync » Async.AwaitTask)
 |&gt; Async.Parallel
 return pages |&gt; Seq.fold (fun accumulator current -&gt; current.Length + accumulator) 0
在C#中的使用.
微软将C#中的async/await模式称作“以任务为基础的异步模式”（Task-based Asynchronous Pattern，TAP）。异步函数的返回值通常包括codice_37、codice_27 、codice_12、 codice_40和codice_41。代码中还可以利用异步函数构造器（async method builders）自行定义异步函数的返回值类型，不过这一场景高阶且少见。返回codice_37的异步函数通常应该是事件监听器，对于一般的函数则应该返回codice_27对象，因为它能够提供更加直观的异常处理。
要在函数中使用codice_2，必须在函数声明前加上codice_1关键字。当需要函数返回codice_12类型的值时，函数声明前要加上codice_1关键字，同时应当返回codice_48或兼容的类型，而非codice_12本身；随后编译器就会将返回的codice_48类型包装为codice_12泛型。不过，当非异步函数（没有使用codice_1声明的函数）返回codice_12时，其值也可以被codice_2。
下列函数代码将使用codice_2从URL下载数据。该函数的逻辑用codice_2实现了同时触发多个任务，无需等待其完成，这使得下一个任务无需在上一个任务完成之后才触发（这是同步的逻辑）。
public async Task&lt;int&gt; SumPageSizesAsync(IEnumerable&lt;Uri&gt; uris) 
 var client = new HttpClient();
 int total = 0;
 var loadUriTasks = new List&lt;Task&lt;byte[]»();
 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模式。它提供了一个名为codice_2的特殊函数。与C#不同的是，Scala中的异步逻辑并不需要用codice_1来标记。通过Scala-async，可以直接将异步逻辑用codice_1函数调用的形式包围起来。
这是如何实现的.
Scala-async所提供的codice_1实际上是通过宏来实现的。编译器会调用不同的代码，然后产生一个有限状态机（通常认为这比单子实现更高效，但是更难以编写）。
在Python中的使用.
在Python中的使用，在语法上与C#、JavaScript等类似。
import asyncio
async def main():
 print("hello")
 await asyncio.sleep(1)
 print("world")
asyncio.run(main())
在JavaScript中的使用.
JavaScript中的codice_2运算符只能用于codice_1标注的函数中，或者用于模块的最顶层。
如果codice_2运算符后跟参数为Promise对象，那么函数逻辑会在该Promise对象被resolve之后继续执行，或者在它被reject以后抛出异常（可以进行异常处理）；如果codice_2运算符后跟参数不是Promise对象，那么该值会被直接返回（不会等待）。
许多JavaScript库提供了可返回Promise对象的函数，它们都可以被codice_2——只要符合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++中是codice_6）。GCC、MSVC编译器支持async/await模式，包括协程以及相关的关键字例如codice_6。Clang对此有部分支持。
值得注意的是codice_68和codice_69虽然看起来像是可以被await求值的对象，但是实际上它们并没有实现任何类似于"从协程中返回的值"或者"可被await的对象"的相关属性；要使返回对象可以被await求值，必须在返回的对象类型上实现一系列的公共成员函数，例如codice_70、codice_71和codice_72等。具体细节可以查看相关的参考。
using namespace std;
CustomAwaitableTask&lt;int&gt; add(int a, int b)
 int c = a + b;
 co_return c;
CustomAwaitableTask&lt;int&gt; test()
 int ret = co_await add(1, 2);
 cout « "return " « ret « endl;
 co_return ret;
int main()
 auto task = test();
 return 0;
在C语言中的使用.
C语言没有对await/async的官方支援。
某些协程库（例如s_task ）通过宏定义的方式，
实现了和其他语言类似的await/async的强制性语义要求，即：
 1. 必须在async标注的函数内，才能调用await；
 2. 等待一个标注为aysnc的函数，调用该函数时需要加上await；
//定义协程任务需要的栈空间
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 &lt; 5; ++i) {
 printf("task %d, delay seconds = %d, i = %d\n", n, n, i);
 s_task_msleep(__await__, n * 1000); //等待一点时间
void main_task(__async__, void* arg) {
 int i;
 //创建两个子任务
 s_task_create(g_stack0, sizeof(g_stack0), sub_task, (void*)1);
 s_task_create(g_stack1, sizeof(g_stack1), sub_task, (void*)2);
 for (i = 0; i &lt; 4; ++i) {
 printf("task_main arg = %p, i = %d\n", arg, i);
 s_task_yield(__await__); //主动让出cpu
 //等待子任务结束
 s_task_join(__await__, g_stack0);
 s_task_join(__await__, g_stack1);
int main(int argc, char* argv) {
 s_task_init_system();
 //创建一个任务
 s_task_create(g_stack_main, sizeof(g_stack_main), main_task, (void*)(size_t)argc);
 s_task_join(__await__, g_stack_main);
 printf("all task is over\n");
 return 0;