tcldr
1
Checking my understanding here:
When spawning an unstructured Task from an actor isolated function, like so:
@MainActor func myFunc() {
Task {
print("hi from the MainActor!")
}
}
The task concurrency context inherits from @MainActor as expected (breaks on main thread).
However, if we add another call that spawns an unstructured Task indirectly, from a @MainActor annotated function, via a non-isolated synchronous function like so:
@MainActor func myFunc() {
Task {
print("hi from the MainActor!")
}
myDelegatedFunc()
}
func myDelegatedFunc() { // not called directly, called via `myFunc()`
Task {
print("hi from the Cooperative Thread Pool!")
}
}
And the new Task inherits launches on the cooperative thread pool. Is that expected?
2 Likes
tcldr
3
The linked article talks about async funcs, this is a synchronous function. Still applies?
tcldr
4
Ok, just found @ole ‘s article Where View.task gets its main-actor isolation from – Ole Begemann
That’s surprising. In my opinion It would be more intuitive if either Task’s always spawned on the cooperative pool, or synchronous non isolated functions inherited their caller’s actor context.
1 Like
@tcldr It is my understanding that's the case. myDelegatedFunc will be called on the main executor, but the Task will be run on the default executor, unless you're marking myDelegatedFunc with @MainActor too.
tcldr
6
Yup, looks like it. Surprised by that. It makes it very difficult to keep execution on the main actor.
It rules out the idea of creating utility code that can run on a chosen global actor unless you can annotate it statically in code. With concerns raised around the performance cost of actor hopping that seems quite a limiting constraint,
bbrk24
7
Would something like this work?
func spawnTaskOnGlobalActor<T: GlobalActor>(_ type: T.Type, body: @Sendable @escaping () async -> Void) {
Task {
await { (_: isolated T.ActorType) in
await body()
}(T.shared)
}
}
@Sendable func myUtilityFunc() async {
print("Hello from wherever I am!")
}
spawnTaskOnGlobalActor(MainActor.self, body: myUtilityFunc)
It's a bit verbose, but it compiles at least, and I think it should work.
1 Like
tcldr
8
That's not working for me, unfortunately. It still runs on the cooperative thread pool. If it did work, I imagine you'd actually get two hops as well. The initial hop on to the cooperative thread pool when the Task is spawned, then another to go back to the GlobalActor when the body is called.
Interesting idea though!
1 Like