suppose we have a non-isolated, synchronous function in which we want to spawn a Task that will execute its work on no actor. an initial approach might look like this:
func spawnBgWorkSync() {
Task { /* ... */ }
}
however, the Task
's closure parameter has the @_inheritActorContext
attribute, which raises the question: is the code in the example guaranteed to never run on an actor?
the actor inheritance proposal (SE-420) states:
Non-isolated synchronous functions dynamically inherit the isolation of their caller. For example, an
actor
method can call a non-isolated synchronous function, and the function will behave dynamically as if it is isolated to the actor.
but it is unclear to me what the implications of this are on the effective runtime isolation of the Task
's operation
parameter, if any.
per SE-338, we can currently ensure that async work occurs off-actor by using a non-isolated async function, or via Task.detached
, so presumably either of the following would achieve the desired goals:
func spawnBgWorkSyncDetached() {
Task.detached { /* ... */ }
}
func spawnBgWorkSync() {
Task { await doTheWork() }
}
private nonisolated doTheWork() async {
// ...
}
but in the non-detached Task case, there is still a lingering question about whether the Task
could somehow inherit actor isolation, which would then necessitate a needless 'hop' to actually execute its effective work.
to rephrase the underlying questions/motivation here a bit –
if one wants to implement a synchronous, non-isolated function that:
- spawns an unstructured
Task
that runs on the global concurrent executor - is resilient to external refactoring affecting its statically-inferred isolation
- is resilient to different caller-isolation affecting its runtime-inferred dynamic isolation
- minimizes needless 'hops' between actors
what are the best tools for the job?