19.4. 并行的调用异步方法

调用有 await 标记的异步函数在同一时间只能执行一段代码。在异步代码执行的过程中,调用方需要等待异步代码执行完后才能继续执行下一行代码。比如,当你想从图库中拉取前三张图片,可以像下面这样,等三次调用完后再执行 downloadPhoto(named:) 函数:

let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])
let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

这种方式有一个非常严重的缺陷:虽然下载过程是异步的,并且在等待过程中可以执行其他任务,但每次只能执行一个 downloadPhoto(named:)。每一张图片只能在上一张图片下载结束了才开始下载。然而,并没有必要让这些操作等待,每张图片可以独立甚至同时下载。

为了在调用异步函数的时候让它附近的代码并发执行,定义一个常量时,在 let 前添加 async 关键字,然后在每次使用这个常量时添加 await 标记。

async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])
let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

在上面的例子中,三次调用 downloadPhoto(named:) 都不需要等待前一次调用结束。如果系统有足够的资源,这三次调用甚至都可以同时执行。这三次调用都没有没标记为 await,因为代码不需要被挂起等待函数的结果。程序会继续执行直到 photos 被定义,与上面不同的是,在这个时间点由于程序需要上面几次异步调用的结果,所以你需要添加 await 关键字来挂起当前代码的执行直到所有图片下载完成。

下面是关于两种不同方法的一些说法:

  • 代码中接下来的几行需要依赖异步函数的结果时,需要使用 await 来调用异步函数。这样产生的结果是有序的。
  • 短时间内并不需要异步函数的结果时,需要使用 async-let 来调用异步函数。这样产生的任务是并发的。
  • awaitasync-let 都允许其他任务在他们被挂起的时候执行。
  • 在两种情况下,都需要用 await 标记可能的悬点,以表明代码在这些点在需要的情况下会被挂起,直到异步函数执行结束。

你也可以在同一段代码中混用两种方式。