Task and Task.detached

Hi,

I have been reading / watching videos about swift concurrency in Swift 6.2 and concepts such as:

  • @MainActor - Run on main thread
  • @concurrent - Run on background thread (hop off hop off calling actor executor)
  • nonisolated(nonsending) - Will continue on the caller's executor

Questions

  1. How does Task { } and Task.detached { } fit with the above concepts?
  2. Will task's closure Task { } be executed in the caller's executor?
  3. Will task's closure Task.detached { } be executed in the background thread?

My understanding is that — by default — it does if it’s created in a statically isolated function, but not if it’s in a nonisolated(nonsending) function. That being said, both Task and Task.detached can explicitly specify where they run, e.g. Task { @MainActor in … }.

1 Like

Thanks @bbrk24,

Yes of course explicitly stating can specify where it would run.

My question was referring to the behavior when not explicity stated.

Based on my experimentation (correct me if I am wrong)

  • Task { } inherits the context when it can, when in an actor, it can inherit the context and takes it, like when called from @MainActor runs on the main thread.
  • Task { } can't inherit a context when called form a nonisolated function and therefore doesn't inherit context and therefore it runs on the background thread

This looks correct to me.

It's also worth noting that detached Tasks have a bunch of other effects besides changing context inheritance, including not sharing Task-local variables with the parent, and not inheriting priority in the same way.

Because of this I'm always pretty skeptical of using detached just to disable inheritance; I would generally prefer moving the code to an @concurrent async function and calling that from the Task (or eliminating the Task entirely if the caller is also async).

If you're familiar with DispatchQueue, the equivalent concept is DISPATCH_BLOCK_DETACHED | Apple Developer Documentation

2 Likes

@David_Smith thanks a lot!!

That is a very clear explanation and thanks for order of preference.

I've never thought about this, but that's interesting with the changed behavior of nonisolated(nonsending)... I mean, during runtime we're always on some isolation context, so what does "can't inherit" mean? Is it dynamically determined, i.e. the Task's closure runs on whatever isolation context the function runs on (which was determined by its caller, up to a root task)? Or is it always a new isolation context?