Introduction
Swift's Task.detached creates a top-level task that is not inheritantly linked to the current task's context. Without explicit priority configuration, detached tasks run at TaskPriority.medium by default, which can cause priority inversion: critical tasks (like UI updates or network responses) are delayed behind low-priority batch operations. This is especially problematic when mixing background processing with user-facing operations.
Symptoms
- UI freezes during background processing despite using async
- Network response processing delayed by file compression tasks
- High-priority tasks take longer than low-priority ones
Task.detachedtasks run in unexpected order- Thread pool saturated with medium-priority work
Debug priority: ```swift // Check current task priority print(Task.currentPriority) // .userInitiated, .medium, .background, etc.
// Task.detached defaults to .medium regardless of context let task = Task.detached { print(Task.currentPriority) // Always .medium unless specified } ```
Common Causes
Task.detachedwithout explicit priority parameter- Background batch processing competing with user-initiated tasks
- Default
.mediumpriority used for time-sensitive operations - Task group inheriting wrong priority from parent
- No priority escalation for dependent tasks
Step-by-Step Fix
- 1.Set explicit priority on detached tasks:
- 2.```swift
- 3.// WRONG - default medium priority
- 4.Task.detached {
- 5.// Time-sensitive work runs at medium priority
- 6.try await processPayment(transaction)
- 7.}
// CORRECT - explicit priority Task.detached(priority: .userInitiated) { // High priority for user-facing operations try await processPayment(transaction) }
// Priority levels (highest to lowest): // .high - Critical user-initiated work // .userInitiated - User-triggered actions // .medium - Default, general purpose // .low - Non-urgent background work // .utility - Maintenance, cleanup // .background - Indexing, syncing, prefetching ```
- 1.Escalate priority for time-dependent work:
- 2.```swift
- 3.class ImageProcessor {
- 4.private var currentTask: Task<Void, Never>?
func processImages(_ images: [Image]) async { // Cancel any existing processing currentTask?.cancel()
currentTask = Task.detached(priority: .userInitiated) { for image in images { if Task.isCancelled { return } await self.processSingleImage(image) } } }
func processThumbnail(_ image: Image) async { // Higher priority than batch processing currentTask?.cancel() // Cancel batch first
Task.detached(priority: .high) { // Thumbnail needed for immediate display await self.processSingleImage(image, quality: .thumbnail) await MainActor.run { self.displayThumbnail(image) } } } } ```
- 1.**Use TaskGroup with per-task priorities":
- 2.```swift
- 3.func processMixedWorkload() async {
- 4.await withTaskGroup(of: Void.self) { group in
- 5.// High priority: user-facing work
- 6.group.addTask(priority: .userInitiated) {
- 7.await refreshUI()
- 8.}
// Medium priority: data sync group.addTask(priority: .medium) { await syncLocalData() }
// Low priority: cleanup group.addTask(priority: .background) { await clearTempFiles() }
// Wait for all to complete await group.waitForAll() } } ```
Prevention
- Always specify priority on
Task.detachedcalls - Use
.userInitiatedor.highfor user-visible operations - Use
.backgroundor.utilityfor maintenance tasks - Cancel lower-priority tasks when higher-priority work arrives
- Monitor task execution times to detect priority inversion
- Document priority conventions for the development team