Leave a rating/review
Use a Dispatch Work Item
Often, you use a dispatch queue to “fire and forget” background tasks. If you want a lot more control over organizing and managing tasks, you’d use operations and operation queues instead — you’ll learn about these in part 3 of this course. But you can get a little more control over dispatch queue tasks with DispatchWorkItems and DispatchWorkGroups. You’ll learn about dispatch work groups in a later video.
You can create and execute a DispatchWorkItem if you want to be notified when the task completes, or if you might need to cancel the task.
Open the playground in the starter folder. Here are the main queue, a queue with userInitiated quality of service, and two tasks:
let mainQueue = DispatchQueue.main
let userQueue = DispatchQueue.global(qos: .userInitiated)
func task1() {
print("Task 1 started")
sleep(4)
print("Task 1 finished")
}
func task2() {
print("Task 2 started")
print("Task 2 finished")
}
In the previous exercise, this is how you ran task 2 in the background:
userQueue.async {
task2()
}
Run this by clicking the run button over the line number. The output appears in the debug area, down here:
>> Just run task 2 in the background.
Task 2 started
Task 2 finished
Now create a DispatchWorkItem with task 1:
let workItem = DispatchWorkItem {
task1()
}
And run it on userQueue:
userQueue.async( // choose async(execute:) from menu
Instead of the async closure you’ve been using, choose this execute method.
userQueue.async(execute: workItem)
And pass your workItem as the execute argument. Click next to this last line here, to run the playground up to this line.
>> Run task 1 as a work item.
Task 1 started
Task 1 finished
It works exactly the same as running task1 by itself.
If the current thread really needs workItem to finish before it can continue, call .wait to tell the system to give it priority. If necessary or possible, the system will increase the priority of other tasks in the same queue. It does not do this when you wait for dispatch groups, which you’ll learn about in a later video.
if workItem.wait(timeout: .now() + 3) == .timedOut {
print("I got tired of waiting.")
} else {
print("Work item completed.")
}
Run the playground up to this line:
>> Waiting for task 1...
Task 1 started
I got tired of waiting.
Task 1 finished
The wait times out but task 1 continues to run and eventually finishes.
Work items have other advantages over simple tasks. First, you can construct a simple dependency, for example, to update the user interface after task 1 completes.
There’s some helper code here. You’ll use it later. Now, define two work items. Later, you’ll add code to see which queue each work item runs on.
let backgroundWorkItem = DispatchWorkItem {
task1()
}
let updateUIWorkItem = DispatchWorkItem {
task2()
}
This code pretends that task2 updates the user interface based on what task1 does. So first, execute backgroundWorkItem on the user queue:
userQueue.async(execute: backgroundWorkItem)
Next, specify that updateUIWorkItem should run after backgroundWorkItem completes:
backgroundWorkItem.notify(queue: mainQueue,
execute: updateUIWorkItem)
The work items can run on different queues. In fact, if updateUIWorkItem updates the user interface, you must run it on the main queue.
- To see proof of which queue each task runs on, scroll back to that helper code:
enum Queues { case main, user }
// TODO later: Set specific key for each queue.
And add these lines below the TODO:
let specificKey = DispatchSpecificKey<Queues>()
mainQueue.setSpecific(key: specificKey, value: .main)
userQueue.setSpecific(key: specificKey, value: .user)
You’ve created a DispatchSpecificKey with values from the Queues enum and set the value of this key for the main and user queues.
Uncomment this helper function. It refers to specificKey so it would have flagged an error before. And now call this function in your work items:
let backgroundWorkItem = DispatchWorkItem {
task1()
whichQueue(workItem: "backgroundWorkItem")
}
let updateUIWorkItem = DispatchWorkItem {
task2()
whichQueue(workItem: "updateUIWorkItem")
}
Scroll down to the .notify statement and run the playground up to this line:
>> Run task 2 work item after task 1 work item finishes.
Task 1 started
Task 1 finished
>> backgroundWorkItem is running on userQueue
Task 2 started
Task 2 finished
>> updateUIWorkItem is running on mainQueue
Task 2 runs after task 1, even though task 1 takes longer. And backgroundWorkItem ran on userQueue, and updateUIWorkItem on mainQueue.
Finally, here’s how you cancel a work item:
if !updateUIWorkItem.isCancelled {
updateUIWorkItem.cancel()
}
You check whether the second work item is cancelled. If not, you cancel it. This is the last bit of code, so just run the playground: Task 2 doesn’t run at all.
>> Run task 2 work item after task 1 work item finishes.
Task 1 started
>> Cancel task 2 work item before task 1 work item finishes.
Task 1 finished
>> backgroundWorkItem is running on userQueue
Task 1 might finish before / after the cancel message prints.
And that’s the end of this video.