DispatchQueue
A class that manage the tasks execution serially or concurrently on your app’s main thread or on a background thread.
Serial queue vs Concurrent queue
Serial queue
Serial queue perform one task at a time (FIFO). One example to this is the main queue.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let queue = DispatchQueue.main
queue.async(execute: DispatchWorkItem(block: {
print("Work started 1")
// Some work....
print("Work ended 1")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 2")
// Some work....
print("Work ended 2")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 3")
// Some work....
print("Work ended 3")
}))
This code will print out:
1
2
3
4
5
6
Work started 1
Work ended 1
Work started 2
Work ended 2
Work started 3
Work ended 3
Concurrent queue
Concurrent queue can perform multiple tasks at the same time. One example to this is the global queue. Tasks in the global queue will start in order but can finish in different order.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let queue = DispatchQueue.global()
queue.async(execute: DispatchWorkItem(block: {
print("Work started 1")
sleep(3) // Some work....
print("Work ended 1")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 2")
sleep(3) // Some work....
print("Work ended 2")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 3")
sleep(3) // Some work....
print("Work ended 3")
}))
This code will print out:
1
2
3
4
5
6
Work started 1
Work started 2
Work started 3
Work ended 1
Work ended 2
Work ended 3
Creating Queues
Create a Serial queue
When you create a new queue by default it will be serial queue, let’s see how to create one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let queue = DispatchQueue(label: "com.queue.serial")
queue.async(execute: DispatchWorkItem(block: {
print("Work started 1")
sleep(3) // Some work....
print("Work ended 1")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 2")
sleep(3) // Some work....
print("Work ended 2")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 3")
sleep(3) // Some work....
print("Work ended 3")
}))
The lable used in the initializer is just to to uniquely identify it in debugging tools such as Instruments, sample, stackshots, and crash reports.
The code above will print as following:
1
2
3
4
5
6
Work started 1
Work ended 1
Work started 2
Work ended 2
Work started 3
Work ended 3
Create a Concurrent queue
To create a concurrent queue there is a one simple value you must pass it to the attributes
which is the concurrent
attribute.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let queue = DispatchQueue(label: "com.queue.concurrent", attributes: .concurrent)
queue.async(execute: DispatchWorkItem(block: {
print("Work started 1")
sleep(3) // Some work....
print("Work ended 1")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 2")
sleep(3) // Some work....
print("Work ended 2")
}))
queue.async(execute: DispatchWorkItem(block: {
print("Work started 3")
sleep(3) // Some work....
print("Work ended 3")
}))
This will print as following:
1
2
3
4
5
6
Work started 1
Work started 2
Work started 3
Work ended 2
Work ended 1
Work ended 3
Async vs sync
When you run a task with sync it will block the current thread until it finish.
1
2
3
4
5
6
7
8
9
let queue = DispatchQueue(label: "com.queue.serial", attributes: .concurrent)
queue.sync(execute: DispatchWorkItem(block: {
print("Work started")
sleep(3)
print("Work ended")
}))
print("Done!")
The code above will print:
1
2
3
4
Work started
// The whole app will stop for 3 seconds then:
Work ended
Done!
Async will start the task and return directly without blocking the thread.
1
2
3
4
5
6
7
8
9
let queue = DispatchQueue(label: "com.queue.serial", attributes: .concurrent)
queue.async(execute: DispatchWorkItem(block: {
print("Work started")
sleep(3)
print("Work ended")
}))
print("Done!")
The code above will print:
1
2
3
Done!
Work started
Work ended
Quality of Service
You give the queue a qos to tell the system how much important is your work, so, the higher is your qos the more the system will try to prioritizes the work.
We have 4 main types of quality of services:
- User interactive: The quality-of-service class for user-interactive tasks, such as a work that the user is expecting to see the result directly.
- User initiated: The quality-of-service class for tasks that is important but not as important as user interactive, maybe a user clicked on a button and he is waiting to see results.
- Utility: The quality-of-service class for tasks that the user does not track actively, but he knows about them.
- Background: The quality-of-service class for the tasks that the user is not aware of.
Another two:
- unspecified: The absence of a quality-of-service class.
- Default: The default quality-of-service class (between Utility and User initiated).
Autorelease frequency
The frequency with which to autorelease objects created by the blocks that the queue schedules.
There is three posible values:
1- .workitem
: which means autorelease each dispatch_async
block (work item). This flag used by the main
queue.
2- .never
: Never autorelease object automaticlly. This flag used by the global()
queue.
3- .inherit
: It means to inherint the autorelease frequence from the target
queue. when you create a new queue using DispatchQueue.init
, the default value for that queue will be .inherit
.
Target
When you set the target, all work items in the queue will be redirected to the target queue. So you can have multiple queues with the same target queue. You might do this to minimize the total number of threads your app uses, while still preserving the execution semantics you need. The system doesn’t allocate threads to the dispatch queue if it has a target queue, unless that target queue is a global concurrent queue.
The target queue defines where blocks run, but it doesn’t change the semantics of the current queue. Blocks submitted to a serial queue still execute serially, even if the underlying target queue is concurrent. In addition, you can’t create concurrency where none exists. If a queue and its target queue are both serial, submitting blocks to both queues doesn’t cause those blocks to run concurrently. The blocks still run serially in the order the target queue receives them.
A dispatch queue inherits the minimum quality-of-service level from its target queue.
https://developer.apple.com/documentation/dispatch/dispatchobject/1452989-settarget
dispatchPrecondition
Dispatch precondition helps to checks for a condition necessary for further execution even in production.
The following code will crash because the condition did not met which is that the current work item should be as a barrier.
1
2
3
4
5
6
7
8
let queue = DispatchQueue(label: "com.queue.concurrent", attributes: .concurrent)
queue.async(execute: DispatchWorkItem(block: {
dispatchPrecondition(condition: .onQueueAsBarrier(queue))
print("Work started 1")
sleep(3) // Some work....
print("Work ended 1")
}))
The following code will work because the condition met, which is that the current work item should be as a barrier.
1
2
3
4
5
6
7
8
9
let queue = DispatchQueue(label: "com.queue.concurrent",
attributes: .concurrent)
queue.async(execute: DispatchWorkItem(flags: .barrier, block: {
dispatchPrecondition(condition: .onQueueAsBarrier(queue))
print("Work started 1")
sleep(3) // Some work....
print("Work ended 1")
}))
-❤️~.
To read more about queues you can check the other articles published in this blog under concurrency
category.
If you have any questions you can send me a message on Twitter or facebook. Also you can check my Github page or my Apps.