Going through the Documentation I notice several instances where it mentions “the current task”, and I just can’t help but wonder if that refers to a Task that’s running the current code or if it can refer to a function that’s running it. For example, in Unstructured Concurrency, it mentions
“To create an unstructured task that runs similarly to the surrounding code, call the Task.init(name:priority:operation:) initializer. The new task defaults to running with the same actor isolation, priority, and task-local state as the current task”
But I’m not sure if “current task” just means the Task that’s running this Task, or if it could mean an instance method that calls to create this Task.. Because I know a top level Task inherits the actor isolation of the function where it was created.
So, what is the “current task” and why is the term so confusing?
Per my understanding, there doesn’t need to be a task which is currently running at all, in which case the first task you create using Task or Task.detached becomes the origin task when speaking in the context of unstructured concurrency (contrast this with structured concurrency and the topic of the “task-tree”, which is referred to in the WWDC video related to structured concurrency). Any subsequent tasks you add within this origin task, which itself can be referred to as the current task, inherit, either using Task, the priority of the origin task, or Task.detached, nothing from the origin task at all.
What I mean is, the doc doesn’t explain what the actor isolation behaviour of a task that has no higher level task spawning it. A standalone task within a function for example. Yet we know this task would inherit the actor isolation of the function. So is the doc just missing that or does “current task” encompass a task spawned from inside a function body?
According to WWDC21: Explore structured concurrency in Swift, a task, even one that has no higher level task spawning it, still inherits the actor, if any, of its launched context, and it also inherits the priority and other traits of the origin task.
The inheritance of an actor determines the data accessible without interrupting the execution of the RunLoop associated with a Thread instance which is managed with a DispatchQueue instance. Crossing a RunLoop boundary can cause a data race as they represent the execution flow of individually exposed system threads.
Task instances are spawned roughly like DispatchQueue.async(execute: .init(qos:,flags:){ })[1]. Having to jump through these hoops would be overwhelming and could lead to mistakes and increases verbosity. Overall Task instances represent multiple scheduling blocks, which are functionally identical to code separated by a suspension point[2], in which each block is then iteratively scheduled when the current suspension operation is completed[3].
Declaring a task manually
DispatchQueue.main.async(execute:
.init {
print("This will run eventually")
}
)
TLDR:
A Task can be confined to the MainActor which runs it with a reference to DispatchQueue.main which then directs a Thread instance to interact with RunLoop.main to schedule the next usable block of the Task instance’s available scheduling blocks for execution by a core which may be specified by the requested TaskPriority[4] of a Task.
flags determines if it is blocking(barrier), detached and how to handle the qos(Quality of Service) parameter ↩︎