GCD DispatchWorkItem Cancellation: Cooperative Cancellation in Swift
These articles are AI-generated summaries. Please check the original sources for full details.
Cancelando un DispatchWorkItem
DispatchWorkItem cancellation is cooperative, not preemptive. The cancel() method sets a boolean flag but cannot stop code already running.
Why This Matters
In Grand Central Dispatch (GCD) on iOS/macOS, developers often assume cancel() abruptly terminates tasks, but it only marks a flag. This misalignment between expected and actual behavior can lead to resource leaks, unresponsive UIs, or infinite loops if the executing block never checks the cancellation state.
Key Insights
- Cancel() before execution: If a DispatchWorkItem is cancelled before it is dispatched, the closure never runs. Example: call workItem.cancel() then queue.async(execute: workItem) results in the block never executing.
- Cancel() during execution: cancel() merely sets isCancelled to true; the onus is on the block to read isCancelled and decide to exit early. In the article’s loop example, the block checks isCancelled at each iteration, stopping after the current iteration completes.
- wait() unaffected by cancellation: Even after cancel(), wait() blocks until the block finishes naturally or never starts—if cancelled before execution, wait() returns immediately; if during, it waits for the block to terminate.
- Variable capture pattern for isCancelled: The DispatchWorkItem reference must be stored as an optional variable before the closure to allow the closure to check workItem?.isCancelled—a common Swift pattern for recursive or self-referencing closures.
Working Examples
A DispatchWorkItem cancelled before being dispatched never executes the closure.
let queue = DispatchQueue(label: "dev.goyes.gcd.workitem-cancel")
let workItem = DispatchWorkItem {
print("Este mensaje nunca se imprime")
}
workItem.cancel()
queue.async(execute: workItem)
A DispatchWorkItem that checks isCancelled on each iteration. After 1.2 seconds, cancel() is called; the loop stops on the next check after the current iteration completes.
var workItem: DispatchWorkItem?
workItem = DispatchWorkItem {
for i in 1...5 {
if workItem?.isCancelled == true {
print("Cancelado, deteniendo en la iteración \(i)")
break
}
print("Iteración \(i)")
Thread.sleep(forTimeInterval: 0.5)
}
}
let queue = DispatchQueue(label: "dev.goyes.gcd.workitem-cancel")
queue.async(execute: workItem!)
Thread.sleep(forTimeInterval: 1.2)
workItem?.cancel()
workItem?.wait()
Practical Applications
- Background database cleanup loops: Check isCancelled at loop iterations to allow graceful exit when user navigates away or the app enters background, preventing wasted work.
- Network request wrapping: Use DispatchWorkItem for timeout-based cancellation. Pitfall: If the block does not check isCancelled, the network call or its callback may continue executing even after logical cancellation, leading to stale UI updates.
References:
Continue reading
Next article
Has AI Changed the Joy of Building? A Developer Reflects on Learning, Struggle, and Satisfaction
Related Content
Swift Protocol Magic: Designing a Reusable Location Tracking System for iOS
Eliminate CLLocationManager boilerplate using a protocol-oriented architecture that handles authorization and location updates in five lines of code for production iOS apps.
Integrating Apple's Server LLM on Private Cloud Compute (PCC)
Apple introduces a server-class LLM on Private Cloud Compute (PCC) featuring a 32K context window for developers at WWDC 2026.
Grouping Tests with @Suite in Swift Testing
Swift Testing’s @Suite organizes tests hierarchically, improving BDD clarity with Gherkin-style labels.