Hey folks,
I'm looking to use AsyncStream
, but I've run into some issues with two possible approaches. I'll paste the code snippet with relevant comments below.
This problem resembles this outstanding issue (`Task { [weak self] in ... }` loses its actor context when called in non-global actor method · Issue #62604 · swiftlang/swift · GitHub), however this appearing in an AsyncStream
adds an additional layer of confusion.
class MyClass {
func doNonisolatedWork(with closure: @escaping () -> Void) { }
}
actor MyActor {
let nonisolatedClass = MyClass()
var mainActorClosure: @MainActor () -> Int = { 1 }
var actorProperty: Int = 0
var stream: AsyncStream<Int>?
// APPROACH #1: Assign property directly
// This error follows my understanding of Swift concurrency: `doNonisolatedWork` closure cannot access actor-isolated properties in a non-isolated context
func storedAsyncStreamProperty() {
stream = AsyncStream { continuation in
nonisolatedClass.doNonisolatedWork { [weak self] in
guard let self else { return }
if actorProperty > 0 { // ❌ Actor-isolated property 'actorProperty' can not be referenced from a nonisolated context
Task {
let value = await self.mainActorClosure()
continuation.yield(value)
}
} else {
continuation.finish()
}
}
}
Task {
guard let stream = self.stream else { return }
for await _ in stream {
// do work with return value here
}
}
}
// APPROACH #2: Create a computed property and store the value later
// It doesn't make sense to me that the compiler allows `doNonisolatedWork` closure to access `actorProperty` in non-isolated context.
// Even though this compiles, `mainActorClosure` executes on a concurrent thread, despite its @MainActor attribute
func computedAsyncStreamProperty() {
var stream: AsyncStream<Int> {
AsyncStream { continuation in
nonisolatedClass.doNonisolatedWork { [weak self] in
guard let self else { return }
if actorProperty > 0 {
Task {
let value = await self.mainActorClosure() // ⚠️ No 'async' operations occur within 'await' expression
continuation.yield(value)
}
} else {
continuation.finish()
}
}
}
}
self.stream = stream
Task {
guard let stream = self.stream else { return }
for await _ in stream {
// do work with return value here
}
}
}
}