Difference of creating regular or detached Task from task-less context?

Viewed 1097

When creating a Task in a task-less context (e.g. from some random AppKit code), is there a difference between creating a detached or a regular task? For example, calling

  • Task.detached
  • or Task.init

Since the task has no actor to inherit from, I figured those 2 calls must be equivalent or are there still implementation differences to consider?

1 Answers

First of all, it is important to note that neither of these create child tasks. A child task is created by structured concurrency constructs like awaiting an async function, async let, or task groups, as opposed to unstructured concurrency like Task.init and Task.detached.

In addition to the structured approaches to concurrency described in the previous sections, Swift also supports unstructured concurrency. [...] To create an unstructured task that runs on the current actor, call the Task.init(priority:operation:) initializer. To create an unstructured task that’s not part of the current actor, known more specifically as a detached task, call the Task.detached(priority:operation:) class method.

This is also documented in Task.init (my bold)

Runs the given nonthrowing operation asynchronously as part of a new top-level task on behalf of the current actor.

You can also see this in action - a child task created by awaiting an async function is cancelled when its parent is cancelled, but a task created by Task.init is not.

let task = Task {
    let notChild = Task {
        await Task.sleep(1_000_000_000)
        if !Task.isCancelled {
            print("Not Child!")
        } else {
            print("Child!")
        }
    }
}
task.cancel()
// prints "Not Child!"
func childTask() async {
    await Task.sleep(1_000_000_000)
    if !Task.isCancelled {
        print("Not Child!")
    } else {
        print("Child!")
    }
}

let task = Task {
    await childTask()
}
task.cancel()
// prints "Child!"

Anyway, back to your question, so what actually is the difference between Task.init and Task.detached? The first quote in my answer addressed that a little bit, and this is also mentioned in the documentation of Task.init too:

Like Task.detached(priority:operation:), this function creates a separate, top-level task. Unlike Task.detached(priority:operation:), the task created by Task.init(priority:operation:) inherits the priority and actor context of the caller, so the operation is treated more like an asynchronous extension to the synchronous operation.

Essentially, it's about the actor on which the task is executed, so even if there is no current task, but there is a current actor, Task.init and Task.detached will still do different things. Task.init will run the task on the current actor, whereas Task.detached will run the task detached from any actor.

See also: https://www.hackingwithswift.com/quick-start/concurrency/whats-the-difference-between-a-task-and-a-detached-task

Related