我需要存储
AsyncIterator
的ThrowingTaskGroup
以供以后使用。我正在演员内部的 async
函数内创建这个迭代器。但是,由于生成的 iterator
是值类型,因此我无法在当前函数的范围之外存储并稍后访问它。
我的方法有本质上的错误吗?
这里有一些代码来说明这个问题:
actor Foo {
private var iterator: ThrowingTaskGroup<String, any Error>.Iterator?
func bar() async throws {
let workItems = ["A", "B", "C", "D"]
try await withThrowingTaskGroup(of: String.self) { group in
workItems.forEach { item in
group.addTask { [unowned self] in
return try await doWork(on: item)
}
}
iterator = group.makeAsyncIterator()
guard let firstItem = try await iterator?.next() else { // Cannot call mutating async function 'next()' on actor-isolated property 'iterator'
throw "An error"
}
// do some work based on first item
}
}
private func atALaterPointInTime() async throws {
while let item = try await iterator?.next() { // Cannot call mutating async function 'next()' on
await doMoreWork(on: item)
}
}
private func doWork(on item: String) async throws -> String {
return item
}
private func doMoreWork(on item: String) async {
// ...
}
}
任务组不应该逃避创建它们的任务,因此这种方法行不通。
假设您希望
bar
在任务组中的第一个任务完成后返回。我将使用顶级任务来运行任务组,并将任务组生成的值提供给 AsyncThrowingStream
,然后可由 atALaterPointInTime
使用。
这是一个包含
Int
任务组的示例。我在这里使用了 Int
,以便每个子任务可以通过等待不同的秒数以不同的时间完成。
actor Foo {
private var stream: AsyncThrowingStream<Int, Error>?
func bar() async throws {
let workItems = [1, 2, 3, 4]
let stream = AsyncThrowingStream { continuation in
Task {
do {
try await withThrowingTaskGroup(of: Int.self) { group in
workItems.forEach { item in
group.addTask { [unowned self] in
return try await doWork(on: item)
}
}
for try await item in group {
continuation.yield(item)
}
continuation.finish()
}
} catch {
continuation.finish(throwing: error)
}
}
}
self.stream = stream
var iter = stream.makeAsyncIterator()
guard let firstItem = try await iter.next() else {
throw URLError(.badURL)
}
print("bar is doing some work with firstItem")
}
func atALaterPointInTime() async throws {
guard let stream else { return }
for try await item in stream {
await doMoreWork(on: item)
}
}
private func doWork(on item: Int) async throws -> Int {
print("Doing work on \(item)")
try await Task.sleep(for: .seconds(item))
print("Work on \(item) is done")
return item
}
private func doMoreWork(on item: Int) async {
print("Doing more work with \(item)")
}
}
这是一个简单的 SwiftUI 视图来调用
Foo.bar
和 Foo.atALaterPointInTime
:
struct ContentView: View {
@State var foo = Foo()
var body: some View {
Button("Do More Work") {
Task {
do {
try await foo.atALaterPointInTime()
} catch {
print(error)
}
}
}
.task {
try! await foo.bar()
}
}
}