- 作者:老汪软件技巧
- 发表时间:2024-09-04 04:02
- 浏览量:
在上一篇文章中提到,如果在不支持并发的同步调用环境中调用一个异步方法,会造成编译错误。今天这篇文章中,我们将介绍如何通过任务和任务组来创建异步函数的使用环境。
任务
Swift 中的任务是 WWDC 2021 引入的并发框架的一部分。任务允许我们从非并发方法创建并发环境,使用async/await调用方法。
当首次使用任务时,你可能会注意到调度队列和任务之间的相似之处。两者都允许在不同线程上以特定优先级调度工作。然而,任务有着明显的不同之处,并通过简化调度队列的繁琐性使我们的开发变得更加轻松。
任务的创建和执行
创建一个任务:
let basicTask = Task {
return "This is the result of the task"
}
print(await basicTask.value)
任务也可以抛出错误:
let basicTask = Task {
// .. perform some work
throw ExampleError.somethingIsWrong
}
do {
print(try await basicTask.value)
} catch {
print("Basic task failed with error: (error)")
}
任务在创建后立即运行,不需要显式启动。因此,只有在需要开始它的工作时才创建任务。
在上一篇文章中描述的 async_let 语法会隐式创建一个子任务。
取消任务
使用 cancel() 来取消任务:
basicTask.cancel()
处理取消任务
checkCancellation() 和 isCancelled 都可以用于检查任务取消,但它们的作用方式有所不同:
Task.checkCancellation():
例子:
Task {
// ... 一些异步操作
try Task.checkCancellation() // 检查是否被取消,如果被取消则抛出错误
// ... 继续执行其他操作 (如果任务没有被取消)
}
2. Task.isCancelled:
例子:
Task {
while !Task.isCancelled { // 循环执行,直到任务被取消
// ... 执行一些操作
try await Task.sleep(nanoseconds: 1_000_000_000) // 模拟 1 秒的延迟
}
// ... 任务被取消后执行一些清理操作
}
3. 总结:
监听取消任务
let task = await Task.withTaskCancellationHandler {
// ...
} onCancel: {
print("Canceled!")
}
// ... some time later...
task.cancel() // Prints "Canceled!"
设置优先级
每个任务都有其优先级,类似于使用任务队列时的QOS,代码如下:
任务状态
Swift 中的 Task 有多种状态,反映了其生命周期中的不同阶段。 可以使用 Task.State 枚举类型来访问这些状态。
任务组
Swift 中的任务组用来组合多个并行任务,并等待所有任务完成后返回结果。可以将任务组看作一个包含多个动态添加的子任务的容器。子任务可以并行或串行执行,但是只有在其子任务完成后,Task Group才会被标记为完成。
如何使用任务组
使用withTaskGroup(of:returning:body:)或withThrowingTaskGroup(of:returning:body:)函数来创建任务组。添加到任务组中的所有子任务都会自动并发的开始执行。
使用任务组,返回最终的结果集,例子如下:
let images = await withTaskGroup(of: UIImage.self, returning: [UIImage].self) { taskGroup in
let photoURLs = await listPhotoURLs(inGallery: "Amsterdam Holiday")
for photoURL in photoURLs {
taskGroup.addTask { await downloadPhoto(url: photoURL) }
}
return await taskGroup.reduce(into: [UIImage]()) { partialResult, name in
partialResult.append(name)
}
}
2. 错误处理, 例子如下:
let images = try await withThrowingTaskGroup(of: UIImage.self, returning: [UIImage].self) { taskGroup in
let photoURLs = try await listPhotoURLs(inGallery: "Amsterdam Holiday")
for photoURL in photoURLs {
taskGroup.addTask { try await downloadPhoto(url: photoURL) }
}
return try await taskGroup.reduce(into: [UIImage]()) { partialResult, name in
partialResult.append(name)
}
}
在上面的例子中,如果子任务失败,任务组不会失败;为了在子任务失败时,任务组也失败,需要在迭代的时候使用next()来代替while reduce(),代码如下:
let images = try await withThrowingTaskGroup(of: UIImage.self, returning: [UIImage].self) { taskGroup in
let photoURLs = try await listPhotoURLs(inGallery: "Amsterdam Holiday")
for photoURL in photoURLs {
taskGroup.addTask { try await downloadPhoto(url: photoURL) }
}
var images = [UIImage]()
/// Note the use of `next()`:
while let downloadImage = try await taskGroup.next() {
images.append(downloadImage)
}
return images
}
next()方法接收来自各个任务的错误,让你可以相应地处理这些错误。在这种情况下,我们将错误传递到组闭包,使整个任务组失败。此时,任何其他正在运行的子任务都将被取消。
避免并发改变
不要在创建 TaskGroup 的任务之外的地方修改它。具体来说,以下行为是不被允许的:
因为并发会导致数据竞争,当多个任务同时修改一个 TaskGroup 时,可能会导致数据不一致,甚至程序崩溃。
取消任务组
你可以使用cancelAll()方法显式地取消任务组中的所有子任务。使用addTaskUnlessCancelled()方法添加的任务,只有在任务组没有被取消的情况下才会开始执行。如果任务组已经被取消,这个任务就不会被添加到任务组中,也不会开始执行。
let photos = await withTaskGroup(of: Optional<Data>.self) { group in
let photoNames = await listPhotos(inGallery: "Summer Vacation")
for name in photoNames {
let added = group.addTaskUnlessCancelled {
guard !Task.isCancelled else { return nil }
return await downloadPhoto(named: name)
}
guard added else { break }
}
var results: [Data] = []
for await photo in group {
if let photo { results.append(photo) }
}
return results
}
关于如何监听取消的任务,请看任务相关部分的讲解。
参考……