No. In general, you can't (and shouldn't) make any assumptions about the thread on which an async function runs. The concurrency runtime will schedule non-isolated async functions and actor functions on any thread in its cooperative thread pool. (Exception: @MainActor functions run on the main thread because MainActor is implemented with a custom executor that guarantees this)
The reason you don't need await here is that Task { … } inherits the actor context from the surrounding context. So the call to increment() doesn't need to perform an actor/executor switch because the task is already isolated to the actor.
In your case, this means you can be certain that the Task { … } won’t start running until f1 completes because both are isolated to the same actor. Again, this doesn't guarantee anything about the thread the code runs on.
I have some doubts, please bear with me, I keep getting confused.
The reason you don't need await here is that Task { … } inherits the actor context from the surrounding context. So the call to increment() doesn't need to perform an actor/executor switch because the task is already isolated to the actor.
Questions
What does inheriting context mean? (I see / hear this in the documentation and WWDC videos but I am not sure I fully understand it)
My interpretation from your answer is that Task { ... } though it could run on a different thread but still it ensures the actor isolation (synchronisation), however detached task doesn't ensure actor isolation. Is my understanding correct?
The context is the current actor (or "no actor" if an unstructured task is started from a non-actor-isolated function). In addition, Task { … } also inherits priority and any task-local values that might have been set from its surrounding context.
Task.detached doesn't inherit any of those things.
Correct.
(Note that the inherited actor isolation does not extend to non-actor-isolated async functions the task calls in its closure. As of Swift 5.7, these will run on the default cooperative executor (not on the actor). Read SE-0338 for the details.)
Thanks a lot @ole for patiently answering my questions.
There is a lot of new stuff I am learning.
I think (I might be wrong) inheriting context will inherit the actor's executor which is what guarantees the actor isolation.
After reading SE-0338 my understanding is that it is to to free up the actor's executor, which earlier once switched to the actor's executor stayed on longer than needed. It is fascinating how much goes on behind the scenes lots more for me to learn and understand.
Yeah, I agree that we don't have a really good name for this.
You should avoid saying "child task" when talking about unstructured concurrency. Child tasks are a concept of structured concurrency (task groups and async let). Their central feature is that the lifetime of the child task is bound to a scope and that the parent task can't leave this scope until all child tasks have completed.
I would call Task { … } an "unstructured task". I think this is good enough to distinguish it from a "detached task". The complication is that both unstructured tasks and detached tasks are a form of unstructured concurrency, but I don't have a better term.