19.5. 任务和任务组

任务(task) 是一项工作,可以作为程序的一部分并发执行。所有的异步代码都属于某个任务。上一部分介绍的 async-let 语法就会产生一个子任务。你也可以创建一个任务组并且给其中添加子任务,这可以让你对优先级和任务取消有了更多的掌控力,并且可以控制任务的数量。

任务是按层级结构排列的。同一个任务组中的任务拥有相同的父任务,并且每个任务都可以添加子任务。由于任务和任务组之前明确的关系,这种方式又被称为结构化并发(structured concurrency)。虽然你需要确保代码的正确性,但任务间明确的父子关系让 Swift 能替你处理一些如扩散取消(propagating cancellation)之类的行为,并且能让 Swift 在编译阶段发现一些错误。

await withTaskGroup(of: Data.self) { taskGroup in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        taskGroup.async { await downloadPhoto(named: name) }
    }
}

如果想更多的了解任务组,可以参考 TaskGroup

非结构化并发

对于并发来说,除了上一部分讲到的结构化的方式,Swift 还支持非结构化并发。与任务组中的任务不同的是,非结构化任务(unstructured task)并没有父任务。你能以任何方式来处理非结构化任务以满足你程序的需要,但与此同时,你对于他们的正确性需要付全责。如果想创建一个在当前 actor 上运行的非结构化任务,需要调用初始化方法 Task.init(priority:operation:)。如果想要创建一个不在当前 actor 上运行的非结构化任务(更具体地说就是游离任务(detached task)),需要调用类方法 Task.detached(priority:operation:)。两种方法都能返回一个能让你与任务交互(继续等待结果或取消任务)的任务句柄,如下:

let newPhoto = // ... 图片数据 ...
let handle = Task {
    return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
let result = await handle.value

如果你想更多的了解游离任务,可以参考 Task

任务取消

Swift 中的并发使用合作取消模型。每个任务都在执行中合适的时间点检查自己是否被取消,并且会用任何合适的方式来响应取消操作。这些方式会根据你所执行的工作分为以下几种:

  • 抛出如 CancellationError 这样的错误
  • 返回 nil 或者空的集合
  • 返回完成一半的工作

如果想检查任务是否被取消,既可以使用 Task.checkCancellation()(如果任务取消会返回 CancellationError),也可以使用 Task.isCancelled 来判断,继而在代码中对取消进行相应的处理。比如,一个从图库中下载图片的任务需要删除下载到一半的文件并且关闭连接。

如果想手动执行扩散取消,调用 Task.cancel()