目录
- 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
}