It seems, the rules for actor inheritance for Task closures and those added with addTask
from a TaskGroup are not the same.
I'm referring to the rules stated in SE-0420, specifically:
According to SE-0304, closures passed directly to the Task initializer (i.e. Task { /here/ }) inherit the statically-specified isolation of the current context if:
the current context is non-isolated,
the current context is isolated to a global actor, or
the current context has an isolated parameter (including the implicit self of an actor method) and that parameter is strongly captured by the closure.
(emphasises mine)
So, why is behaving TaskGroup differently? (specifically, ignoring or not compiling "and that parameter is strongly captured by the closure or event")
Here's an example that demonstrates my observations:
enum Test {
// All fine!
static func testTask<T>(
value: T,
isolated: isolated any Actor = #isolation
) {
print(isolated)
Task {
_ = isolated
await sending(value)
}
}
static func testTaskGroup<T>(
value: T,
isolated: isolated any Actor = #isolation
) async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { // Error: Passing closure as a 'sending' parameter risks causing data races between 'isolated'-isolated code and concurrent execution of the closure
_ = isolated
await sending(value) // Sending 'value' risks causing data races
}
while ((try await group.next()) != nil) {}
}
}
static func sending<T>(_ value: sending T) async {}
}
The broader context is:
I'm providing static utility functions which have an isolated parameter. These are defined in a library. Users can specify the actor context, either by having it associated to the global actor or executing from an Actor instance.
I also tried to use async let
assigning it the result of an async function with an isolated any Actor
parameter. But there's also a issue, which is somewhat related:
It seems, the issue is due to current limitations in the compiler. Unfortunately, this excludes a whole class of useful applications.
I'm aware that upcoming features which give better control of the isolation may provide a solution in the future. But currently, the TaskGroup as a whole is not working in this usage scenario at all.
Is there a workaround for this scenario?