Reputation: 902
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.
Upvotes: 14
Views: 5665
Reputation: 437632
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.
Upvotes: 19
Reputation: 17721
Synchronous and asynchronous execution has nothing to do with the underlying queue. Synchronous execution means, that the calling thread must wait until the block is finished. Thus, the second block is enqueued after the first block has finished. Asynchrounous means, that the caller must not wait for the completion of the block. Thus, the third block is enqueued directly after the preceeding serialQueue.async
statement while the second block is still running or even waiting for execution.
At the end of your function serialTask()
it is guaranteed that the first block is executed. The second and the third blocks are enqueued, but it is not sure if they are executed, running or even waiting for execution. Since you're using a serial queue, it is sure that the second block is executed before the third block.
You may check the asynchronous execution of the two last blocks by adding
serialQueue.async {
print "sleep"
sleep(10);
print "awake"
}
before the two calls of async
, and you will observe the following:
sleep
will be printed immediately.serialTask()
takes considerably less than 10 seconds.awake
is only output after 10 seconds (surprise, surprise).serialTask()
ended.This late execution of the two blocks means asynchronous. If you're moving your first (synchronous) block to the end of serialTask()
, the following will happen:
sleep
will be printed immediately.serialTask()
takes approximately 10 seconds. It is finished after the synchronous block at the end is executed.awake
is only output after 10 seconds (surprise, surprise).awake
is printed out.Upvotes: 4
Reputation: 2882
So the sync task runs immediately, then when it completes, you add two async tasks to that queue, *and then your method returns. *Those two tasks will then get processed in order, as soon as that queue is idle.
So if you had any pending async tasks in that queue before this method is called, those tasks will run before your two async tasks.
Upvotes: 0