I am using a serial queue with QoS background
let serialQueue = DispatchQueue(label: "queue1", qos: DispatchQoS.background)
Assigning one jobs synchronous, and two jobs asynchronous:
func serialTask() {
serialQueue.sync {
for i in 0..<10 {
print("🔷", i)
}
}
serialQueue.async {
for i in 20..<30 {
print("⚪️", i)
}
}
serialQueue.async {
for i in 101..<120 {
print("🔷", i)
}
}
}
All 3 jobs are executing sync one after another, but last two jobs are async. Are async jobs sync in serial queue's.
Asynchronous means out of line, synchronous means in line. You can perform synchronous tasks and block multiple threads at once. If you are in a background thread and want to update a whole bunch of the user interface you call out to the main thread in a dispatch queue.
Concurrent queues (also known as a type of global dispatch queue) execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue.
There are two types of queues, serial and concurrent, but all queues are concurrent relative to each other. The fact that you want to run any code "in the background" means that you want to run it concurrently with another thread (usually the main thread).
A Serial queue allows us to perform only one task at a time, no matter the way of execution, i.e. Synchronous or Asynchronous. All the queues need to wait for the completion of the previous queue. By default, DispatchQueue is a serial queue. E.g. let queue = DispatchQueue(label: "com.swiftpal.dispatch.serial") queue.
Let me see if I can clarify the difference between async
vs. sync
.
A couple of changes that I will employ in my example:
I will use Instruments’ “Points of Interest” to show when tasks are running rather than print
statements. (See WWDC 2019 Getting Started With Instruments.) This way we can see the behavior graphically.
I will post a simple “Point of Interest” event signpost (Ⓢ) when dispatching something and I will wrap the dispatched task in a “Region of Interest” (a horizontal bar) to graphically illustrate the duration of some process.
I'll change your for
loops to be a Thread.sleep(forTimeInterval: 1)
, simulating some time consuming process. If you just have a quick for
loop, things will happen so quickly that it will be impossible to discern what's really happening with the threads.
So, consider:
import os.signpost
private let pointsOfInterest = OSLog(subsystem: "GCD Demo", category: .pointsOfInterest)
func tasks(on queue: DispatchQueue) {
pointsOfInterestRange(with: "tasks(on:)") {
os_signpost(.event, log: pointsOfInterest, name: "1") // first Ⓢ
queue.sync { self.oneSecondProcess(with: "1") }
os_signpost(.event, log: pointsOfInterest, name: "2") // second Ⓢ
queue.async { self.oneSecondProcess(with: "2") }
os_signpost(.event, log: pointsOfInterest, name: "3") // third Ⓢ
queue.async { self.oneSecondProcess(with: "3") }
}
}
func oneSecondProcess(with staticString: StaticString) {
pointsOfInterestRange(with: staticString) {
Thread.sleep(forTimeInterval: 1)
}
}
func pointsOfInterestRange(with staticString: StaticString, block: () -> Void) {
let identifier = OSSignpostID(log: pointsOfInterest)
os_signpost(.begin, log: pointsOfInterest, name: staticString, signpostID: identifier)
block()
os_signpost(.end, log: pointsOfInterest, name: staticString, signpostID: identifier)
}
That is just like your example, but rather than print
statement, we have signposts statements, yielding the following graphical timeline in Instruments’ “Points of Interest” tool:
So, you can see that:
The tasks(on:)
function, on the bottom, issued the sync
dispatch, the first Ⓢ signpost.
It waits for the sync
task, “1”, to finish before continuing, at which point it issues the two subsequent dispatches, the second and third Ⓢ signposts (which happen so quickly in succession that they overlap in the graph).
But tasks(on:)
doesn't wait for the two async
tasks, “2” and “3”, to finish. As soon as it finished dispatching those async
tasks, it immediately returns (hence the tasks(on:)
range stops immediately at that point).
Because the background queue was serial, the three dispatched tasks (“1”, “2”, and “3”) run sequentially, one after the other.
If you change this to use a concurrent queue, though:
let queue = DispatchQueue(label: "...", attributes: .concurrent)
Then you can see that the two async
tasks now run concurrently with respect to each other:
This time, task(on:)
dispatches the sync
call, waits for it to finish, and then, only when that sync
call is done can seriesOfTasks
proceed to dispatch the two async
calls (in this case, not waiting for those to dispatched tasks to finish).
As you can see, the async
and sync
behavior is different. With sync
the calling thread will wait for the dispatched task to finish, but with async
, it won't.
There are two main conclusions that one can draw from the above:
The choice of sync
vs async
dictates the behavior of the current thread (i.e. should it wait for the dispatched task or not).
And, as a general rule, we would generally avoid calling sync
from the main thread when doing anything time consuming (because that would end up blocking the main thread).
The choice of a serial queue vs a concurrent queue dictates the behavior of the work you dispatched, namely can it run concurrently with respect to other tasks on that queue, or will they run consecutively, one after the other.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With