Home What are Dispatch queues and how to use them
Post
Cancel

What are Dispatch queues and how to use them

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.

This post is licensed under CC BY 4.0 by the author.