结构化并发: 一种组织并发任务的编程范式,强调任务之间的明确父子关系和生命周期管理,任务形成明确的父子关系树,父任务取消会自动传播到所有子任务, 作用域结束时也会自动等待所有子任务完成
非结构化并发: 更自由的并发任务管理,需要各自管理生命周期,可以存储任务引用,更加灵活
Swift并发中,结构化并发的另种方法: task group & async let
1. TaskGroup 话不多说上代码
swift
struct TestTaskGroup {
func test() async {
print("Start")
let result = try? await withThrowingTaskGroup(of: Int.self) { group in
for i in 0 ..< 10 {
group.addTask(priority: .high) {
let result = try await calculate(i)
return result
}
}
print("Task added")
for try await result in group {
print("Get result:\(result)")
}
print("Task ended")
return value
}
print("End")
}
private func calculate(_ value: Int) async throws -> Int {
print("Start calculate\(value)")
try await Task.sleep(nanoseconds: UInt64(value * 5) * NSEC_PER_SEC)
print("任务是否被取消 \(try Task.isCancelled)")
print("calculate\(value) done")
return value
}
}
log输出:

可以看到任务在调用test的时候已经开始了 并且是并发执行的,group中的每个子任务都拥有返回值,并且group子任务返回值类型是一致的这点很有限制相对于async let 这点之后会讲, 如果我们想获取到所有子任务值之后应该怎么做呢 我们来改动一下代码如图:


这里上传了图1和图2 强调一下图1这种实现是不安全的 不推荐的
如上图我们所写要求group中的返回值是统一的例子中都是Int, 另外如果任何一个子任务抛出异常 整个taskGroup都会异常,所以一定要注意单个异常的捕获 如下异常处理:
swift
private func calculate(_ value: Int) async throws -> Int {
print("Start calculate\(value)")
try? await Task.sleep(nanoseconds: UInt64(value * 2) * NSEC_PER_SEC)
if value == 2 {
print("Work\(value) throwing error")
throw NSError(domain: "com.example.error", code: 1, userInfo: [NSLocalizedDescriptionKey: "Task \(value) failed"])
}
print("calculate\(value) done")
return value
}
func calculateTest() async {
print("Start")
do {
let result = try await withThrowingTaskGroup(of: Int.self) { group in
var value = 0
for i in 0 ..< 3 {
group.addTask(priority: .high) {
do {
let result = try await calculate(i)
return result
} catch {
print("Error while processing results: \(error)")
throw error // 重新抛出以取消整个任务组
}
}
}
print("Task added")
do {
for try await result in group {
value += result
print("Get result:\(result) value is \(value)")
}
} catch {
print("Error while processing results: \(error)")
throw error
}
print("Task ended")
return value
}
print("End result is \(result)")
} catch {
print("Caught error: \(error)")
}
}
2. async let 是另外一种创建结构化并发子任务的方式,他的返回值类型不必是同样的类型,提高了灵活性
swift
func asyncLet() async throws {
print("Program started")
async let result0 = calculate(0)
async let result1 = calculate(1)
async let result2 = calculate(2)
print("Tasks started concurrently")
// 等待所有任务完成
let (r0, r1, r2) = try await (result0, result1, result2)
print("Program ended")
}
从代码中可以看到,aysnc let 语法比较简洁,类似普通声明变量,只不过这里是一个异步函数,声明时立即开始并发执行,可以处理不同类型的返回值,需要明确使用try await 等待所有结果
3.两者对比:
- group可以动态的表达任务的数量 通过一个for循环来创建所有的任务,但是他要求所有返回值都是同一个类型
- asyn let提高了灵活性,但是对于动态任务组表达比较繁琐,比较适合处理固定数量的异步任务