如何在actor隔离的函数中直接使用异步迭代器?
我恰好需要只消耗一个异步序列元素,所以与其用一个 for await 循环,不如调用 makeAsyncIterator,再调用 next,以明确地表示我根本没有在循环。
@MainActor // this method needs to be main actor isolated
func f(seq: some AsyncSequence<Int, any Error>) async throws {
var iter = seq.makeAsyncIterator()
print(try await iter.next())
}
这会产生一个错误,
发送 'iter' 可能导致数据竞争
我知道它在说什么——next 是一个非隔离的函数,因此我实际上把主执行体被隔离的 iter 发送给非主执行体。因为异步迭代器不可发送,这是一个错误。
但如果我使用一个 for await 循环,代码就能编译通过!
@MainActor
func f(seq: some AsyncSequence<Int, any Error>) async throws {
for try await x in seq {
print(x)
break
}
}
for await 循环本质上应该只是对重复调用 iter.next() 的while循环的语法糖,那么 for await 有什么不同呢?
顺便提一句,我还尝试了
print(try await seq.first(where: { _ in true }))
也同样没能编译——first(where:) 是非隔离的,因此我发送的是不可发送的 seq。
解决方案
有一个 next 的重载,它接受一个 isolation 参数。你应该把当前的隔离状态(写作 #isolation)传给它。
@MainActor
func f(seq: some AsyncSequence<Int, any Error>) async throws {
var iter = seq.makeAsyncIterator()
print(try await iter.next(isolation: #isolation))
}
在许多其他方法(例如 withTaskGroup、withCheckedContinuation)中,isolation 参数的默认值是 #isolation,这就是在使用那些方法时不会遇到类似问题的原因。
另一方面,这个 next 的重载是在后期添加的,见 SE-0421。因此,对于 next(isolation:) 重载来说,给它一个默认参数值是没有意义的,因为如果不提供参数,它总会解析为无参数的 next()。如果去掉无参数版本 next(),将会破坏与旧代码的二进制兼容性。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。