Swift 5.5带来了async/await和actor支持

8,567 阅读3分钟

WWDC21上,苹果公司推出了Swift 5.5,可用于测试。在其新功能中,最令人期待的是使用aysnc/await 和行动者的更好的并发性支持

苹果表示,异步功能q旨在使并发的Swift代码更容易编写和理解。传统上,Swift使用闭包和完成处理程序来处理异步操作。众所周知,当你的代码有许多异步操作,或者控制流变得复杂时,这种方法很快就会导致 "回调地狱"。

Swift的异步函数则为语言带来了循环线

函数可以选择成为异步的,允许程序员使用正常的控制流机制来组成涉及异步操作的复杂逻辑。编译器负责将一个异步函数翻译成适当的闭包和状态机集合。

下面的代码片断显示了如何声明和调用async ,就像它们是同步的一样。

func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image

func processImageData() async throws -> Image {
  let dataResource  = try await loadWebResource("dataprofile.txt")
  let imageResource = try await loadWebResource("imagedata.dat")
  let imageTmp      = try await decodeImage(dataResource, imageResource)
  let imageResult   = try await dewarpAndCleanupImage(imageTmp)
  return imageResult
}

虽然异步函数似乎大大简化了并发管理,但它们并没有排除死锁或状态损坏的可能性。特别是,程序员应该注意到 暂停点异步函数带来的问题。在一个暂停点,一个函数放弃了它的线程。例如,当你调用一个与不同执行上下文相关的异步函数时,就会发生这种情况。为了避免死锁或数据损坏的风险,异步函数应该避免调用可能阻塞其线程的函数。

例如,获取一个mutex只能阻塞到当前运行的某个线程放弃mutex;这有时是可以接受的,但必须谨慎使用,以避免引入死锁或人为的可扩展性问题。相反,等待一个条件变量可以阻塞,直到一些任意的其他工作被安排为信号变量;这种模式强烈反对推荐。

这个功能的一个有趣的演化,使调用异步的Objective-C API成为可能,这些API使用完成处理程序,使用await 表达式。

另一方面,行动者是建立在asyncawait 上的抽象,可以安全地访问可变的状态。简而言之,行为体封装了一些状态,并提供了一组方法来安全地访问它。

与类不同的是,行为体每次只允许一个任务访问其可变状态,这使得多个任务的代码可以安全地与行为体的同一个实例进行交互。

这是一个Swift actor的例子。

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }
}

行为体的方法可以从行为体内部同步或异步地使用,但编译器会强迫你使用异步操作从行为体外部读取行为体的状态。

如果你有兴趣学习Swift并发性在幕后的工作原理,了解Swift任务与Grand Central Dispatch的区别,以及如何在编写Swift并发性代码时考虑到性能,请不要错过苹果WWDC会议的Swift并发性。幕后花絮。

Swift 5.5目前作为Xcode 13测试版的一部分,可以从苹果开发者网站下载。