Edward Brey
Edward Brey

Reputation: 41648

Default priority of Task.detached

When you create a detached task with detached(priority:operation:) but leave priority set to nil, what priority does iOS assign?

For example, suppose a photo output handler calls an actor like this:

class PhotoViewController: UIViewController {
    func photoOutput(_ photoOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        let photoWithContext = someCallToMainActor(photo)
        Task.detached {
            let result = async someCallToAnotherActor(photoWithContext)
            async anotherCallToMainActor(result)
        }
    }
}

The docs for detached(priority:operation:) say about priority, “You need to handle these considerations manually with a detached task.” Since the detached task isn’t a child task, I don’t get the feature that, “Child tasks inherit the parent task’s priority”.

I realize that I can specify the priority explicitly. Still, there presumably is a reason that detached(priority:operation:) allows priority to be nil, and one would expect a well defined semantic. Are there any rules that govern the detached task’s priority?

Upvotes: 0

Views: 837

Answers (2)

Cristik
Cristik

Reputation: 32783

TL&DR; Detached tasks with no specified priority end up having the .medium priority, at least this holds true at the time of writing this answer. Things can change in the future, however not sure how likely is for this one to change.


One of the many nice things about Swift is that it's open source, so we can take a look and see what happens under the hood.

If we take a look at the Task.detached() implementation we can see the following code:

public static func detached(
  priority: TaskPriority? = nil,
  operation: __owned @Sendable @escaping () async -> Success
) -> Task<Success, Failure> {
#if compiler(>=5.5) && $BuiltinCreateAsyncTaskInGroup
  // Set up the job flags for a new task.
  let flags = taskCreateFlags(
    priority: priority, isChildTask: false, copyTaskLocals: false,
    inheritContext: false, enqueueJob: true,
    addPendingGroupTaskUnconditionally: false)

The priority gets passed to the taskCreateFlags() function, which currently looks like this:

func taskCreateFlags(
  priority: TaskPriority?, isChildTask: Bool, copyTaskLocals: Bool,
  inheritContext: Bool, enqueueJob: Bool,
  addPendingGroupTaskUnconditionally: Bool
) -> Int {
  var bits = 0
  bits |= (bits & ~0xFF) | Int(priority?.rawValue ?? 0)

  ...
}

So, in case the priority argument is nil, the first byte of the task flags, which represent the priority, will be zero.

Now, if we continue digging, we reach the compiler area, and at some point we'll reach to Task.cpp:755, which reads

if (isUnspecified(basePriority)) {
   basePriority = JobPriority::Default;
}

, and so happens that JobPriority::Default corresponds to the value 0x15, which, coincidentally or not, and as others have noticed, correspond to the .medium task priority.

Upvotes: 0

Soumya Mahunt
Soumya Mahunt

Reputation: 2762

For child tasks the priority of task is inherited from parent task, and for both detached task and a parent task if priority provided is nil, TaskPriority.medium is used as priority.

Upvotes: 0

Related Questions