改变参与者隔离属性上的异步函数

问题描述 投票:0回答:1

我需要存储

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 {
        // ...
    }
}

swift actor structured-concurrency
1个回答
0
投票

任务组不应该逃避创建它们的任务,因此这种方法行不通。

假设您希望

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()
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.