'current actor' semantics question

hello! i'd basically like to paraphrase this stack overflow question regarding a question around the semantics of the phrase 'current actor'. to quote from the original post (emphasis mine):

Before, I thought I had understood what "current actor" meant:

  • if the code runs on the main queue, then the current actor can be thought of as the MainActor, since its executor is the main queue.
  • if the code is marked with a global actor attribute, then its current actor is that global actor
  • if the code is in an actor and is not nonisolated, then its current actor is that actor.
@SomeOtherActor
func somethingElse() {
    DispatchQueue.main.async {
        // current actor is MainActor
    }
}

@MainActor
class Foo: NSObject {
    func foo() { /* current actor is MainActor */ }
}

actor Bar {
    func foo() { /* current actor is Bar */ }
}

However, recently I realised that I forgot to consider a situation - what happens on a global queue (or any other random queue)?

@SomeOtherActor
func somethingElse() {
    DispatchQueue.global().async {
        // current actor is?
    }
}

this question and examples reflects my current mental model, so please do correct anything if it's inaccurate. thanks in advance!

2 Likes

Global queues are outside of the Swift concurrency model, so you are not in any actor. This is as though you were within a Swift function marked async but declared with no actor attributes and outside actor scope.

i see, thanks for clarifying. is the same true of, say, a custom dispatch queue as well (vs the global ones)?

so in cases where we have something like this:

DispatchQueue.global().async {
  Task {
    // doesn't run on behalf of any actor, so where does this run?
  }
}
  1. can we reason about the execution context of the Task at all?
  2. if there is no current actor, how is the executor determined?
  3. in such scenarios (outside actor scope), are Task.init and Task.detached basically equivalent?

Yes.

We can! The Task, being unstructured, runs on the co-operative thread pool, just like any other Swift Task. It does not run on any actor, but what @Douglas_Gregor calls the "sea of concurrency".

This code pattern is a bit unfortunate: it asyncs from your current context into a global thread pool (one thread hop), in order to spawn a Task whose code will run in the cooperative thread pool (another thread hop).

The executor is the default one, the cooperative thread pool.

This is actually two questions, and they have different answers. In the code you wrote above, yes: the two are equivalent.

In general, the answer is no, they are not equivalent. Creating unstructured Tasks via Task.init does not only inherit actor context, it also inherits task local values. Task.detached does not.

3 Likes