Swift 并发编程核心概念详解

Viewed 0

目录

  • async await
  • async let
  • Task
  • AsyncStream
  • TaskGroup
  • Task Cancellation
  • actor
  • GlobalActor
  • MainActor
  • Sendable

async await

异步函数(方法,属性)是 Swift 并发所引入的一种新的函数类型。

异步函数在声明时用 async 关键字来标记。异步函数具有中断(挂起)和恢复功能,代码中的中断点(暂停点)用 await 关键字来标记。异步函数在中断并恢复前后所处的线程可能会有所不同。

允许调用异步函数或异步方法的场所包括:其他异步函数(方法,属性)的函数体、main 函数以及 Task 闭包中的代码。

// 声明异步函数时使用 async
func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}
// 调用异步函数时使用 await
// 调用 Task.yield 方法可以提交控制权
func generateSlideshow(forGallery gallery: String) async {
    let photos = await listPhotos(inGallery: gallery)
    for photo in photos {
        // ... render a few seconds of video for this photo ...
        await Task.yield()
    }
}
// 声明会抛异常的异步函数时使用 async 和 throws
// 调用会抛异常的异步函数时使用 try 和 await
// 调用 Task.sleep 方法可以让异步函数中断执行一定时间
func listPhotos(inGallery name: String) async throws -> [String] {
    try await Task.sleep(for: .seconds(2))
    return ["IMG001", "IMG99", "IMG0404"]
}

async let

通过 async let 可以并发执行多个异步任务,提高效率。例如,在下载图片时,同步执行需要依次等待每张图片下载完成,而异步执行可以同时下载多张图片。

// 同步执行,依次下载三张图片
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)
// 异步执行,同时下载三张图片
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)

Task

Swift 的并发代码通常在 Task(任务)中执行。启动新的 Task 可以使用 Task.init 方法(在当前 actor 中执行)或 Task.detached 方法(不在当前 actor 中执行)。Task 的作用与 GCD 中的 DispatchQueue.global().async/sync 相当。

// 调用 Task.init 方法启动带返回值的 Task
// 使用 await 等待 Task 执行完成并取得结果
let newPhoto = // ... some photo data ...
let handle = Task {
    return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
let result = await handle.value

AsyncStream

TaskGroup

TaskGroup 可以用来创建具有父子关系的任务组,任务组中的子任务必须具有相同类型,即所有子任务都返回相同类型的结果。任务组通常用来并发执行个数不确定的多个子任务,其作用与 GCD 中的 DispatchGroup 相当。

// 创建任务组来同时下载个数不确定的图片
await withTaskGroup(of: Data.self) { group in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        group.addTask {
            return await downloadPhoto(named: name)
        }
    }
    for await photo in group {
        show(photo)
    }
}

Task Cancellation

Task 在启动之后可以被取消,通过取消机制可以管理任务的执行。

let task = await Task.withTaskCancellationHandler {
    // ...
} onCancel: {
    print("Canceled!")
}
// ... some time later...
task.cancel()  // Prints "Canceled!"

actor

actor 是 Swift 并发所引入的一种新的数据类型,可以看作线程安全的 class,使用 actor 可以防止数据竞争。actor 是引用类型,但不可继承。actor 的属性以及方法缺省具有 isolated(隔离)特性,在读取 actor 中具有隔离特性的属性以及调用 actor 中具有隔离特性的方法时需要用 await 来标记。不能在 actor 之外更新 actor 中具有隔离特性的属性,可以使用 nonisolated 关键字来标记不需要隔离特性的属性或方法。actor 的作用与 GCD 中的 DispatchQueue.init 相当。

// actor 本质上是一种线程安全的 class
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
    }
}
// 在 actor 之外读取 actor 的属性时需要 await
let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(await logger.max) // Prints "25"
// actor 中的方法在存取 actor 自己的属性时不需要 await
extension TemperatureLogger {
    func update(with measurement: Int) {
        measurements.append(measurement)
        if measurement > max {
            max = measurement
        }
    }
}

GlobalActor

MainActor

需要在 main 线程中运行的代码(比如 UI 的更新)以及使用的数据属性可以使用 MainActor 标记。MainActor 是一种预定义的 GlobalActor,通常用来标记与 UI 密切相关的 ViewModel 类,其作用与 GCD 中的 DispatchQueue.main.sync/async 相当。

// 使用 MainActor 标记 ViewModel 类,该类需要在 main 线程中被使用
@MainActor
final class HomeViewModel {
    // ..
}
// 使用 MainActor 标记 ViewModel 类中的数据成员,该数据成员需要在 main 线程中被使用
final class HomeViewModel {
    @MainActor var images: [UIImage] = []
}
// 使用 MainActor 标记函数,该函数需要在 main 线程中被执行
@MainActor
func fetchImage(for url: URL) async throws -> UIImage {
    let (data, _) = try await URLSession.shared.data(from: url)
    guard let image = UIImage(data: data) else {
        throw ImageFetchingError.imageDecodingFailed
    }
    return image
}
// 直接调用 MainActor.run 方法在 main 线程中执行更新 UI 的代码
Task {
    await someHeavyBackgroundOperation()
    await MainActor.run {
        // Perform UI updates
    }
}
// 使用 MainActor 标记 Task,该任务中的代码需要在 main 线程中被执行
Task { @MainActor in
    // Perform UI updates
}

Sendable

0 Answers