I feel I might be missing something obvious, but I can't find a good resource explaining the best way to guarantee that an actor uses a background thread. From what I've gathered, if you create your actor in a detached task then it will stay isolated in the specified priority. So something like:
@MainActor
func foo() async {
/// Technically, "same executor as the main actor"?
let actorWhichExecutesOnTheMainActor = MyActor()
let actorWhichExecutesInTheBackground = await Task.detached(priority: .background) {
MyActor()
}.value
/// body of `synchronousFunction` executes in the background?
let foo = await actorWhichExecutesInTheBackground.synchronousFunction()
/// body of `synchronousFunction` executes on the main thread?
let foo = await actorWhichExecutesOnTheMainActor.synchronousFunction()
}
I've also seen some folks recommending defining a custom @globalActor, but I'm not clear if this is sufficient to guarantee that this global actor runs on a background thread. You can also define a custom executor using a framework like Dispatch, which seems like it would work but feels like a bunch of ceremony just to guarantee an actor is executed off of the main thread.
Am I understanding this correctly? Is there consensus on what is the correct approach?
Every actor which is not the main actor executes on a background thread; this is actually a public guarantee and is stated in the documentation: Actor | Apple Developer Documentation. More specifically, if an actor doesn't specify a custom executor, it's given a default executor instance, which simply delegates to that background thread pool.
I think you're assuming that an actor becomes "bound" to whatever place it has been created in, but this is not true; an actor is always its own isolation domain regardless of whether its initializer got called by something running on the main actor or not, and because it already is concurrency-safe, it can execute concurrently with its creator ā through using the global threadpool.
The good news is that you don't have to do anything Looks like you want precisely the default behaviour.
Also, priority is a property of a task, not an actor. That is, if you call the same actor method twice from tasks with different priorities, like so:
If you do find documentation that suggests otherwise, definitely point it out so we can get it fixed! This is a surprisingly common question, which makes me wonder if there's something documented somewhere that's either incorrect or unclear.
Honestly Iām pretty sure I knew this at some point in the past, but going deeper on some implementation details of structured concurrency, particularly isolation / custom executors (incorrectly) broke my understanding.