Demystifying DispatchQueue.main.sync { }
What is the real use case of DispatchQueue’s sync { }
block
What I knew already:
- DispatchQueues are offerings of the Apple Ecosystem to enable concurrent programming.
- The OS provides 1 serial queue
DispatchQueue.main
and 4 concurrent queuesDispatchQueue.global(qos: Type)
- AnyDispatchQueue.async { //code } dispatches the block of code asynchronously
- AnyDispatchQueue.sync {//code } dispatches the block of code synchronously
Well, thats not all true.
What does async { } block actually do?
When I say dispatchQueue.async { someTask }
, I’m telling the queue that’s currently running, to dispatch someTask
in the dispatchQueue
and continue executing.
1. print("prior work")
2. DispatchQueue.global(qos: .userInitiated).async {
3. print("some other work")
4. }
5. print("posterior work")
Here is the step by step process of what happens when the above block of code gets executed on main
thread.
Main
thread printsprior work
Main
thread schedules the code block[lines 2 to 4] onglobal
Thread and continues its execution(because of theasync
keyword)Main
thread printsposterior work
Global
thread executes the block and printssome other work
So,
async
keyword doesn’t tellglobal
thread (the thread on which task is scheduled) to execute the code block [lines 2 to 4] asynchronously, rather it commands themain
thread (the thread thats running this whole block) to continue its work, without setting itself inblocked
mode till the code block [lines 2 to 4] is executed byuserInitiated
global thread.
What does sync { } block actually do?
The opposite of what’s said above. A sync { }
tells the executing thread to set itself to blocking mode until the sync { }
block is finished executing. Let’s consider a similar kind of code block as above with minor difference.
1. print("prior work")
2. DispatchQueue.global(qos: .userInitiated).sync {
3. print("some UI work")
4. }
5. print("posterior work")
Consider this code block is running on main
thread. The step by step execution is as follows.
- Main thread prints
prior work
- Put the
main
thread on blocking mode (because of thesync
keyword) and start executing the code block [lines 2 to 4] onglobal
thread - Global thread prints
some UI work
- The main thread gets unblocked as
global
thread finished execution - Main thread prints
posterior work
The
sync {}
block enforces running thread (main thread in this case) to enter into blocking/waiting mode until the scheduled code block is executed and continue its execution[line 5] once the scheduled block[lines 2 to 4] executed successfully.
Some more stuff:
Whenever there’s a UI work needs to be done and I’m not sure which thread will execute the desired block of code, I tend to add DispatchQueue.main.async { //Update UI }
to ensure that my UI code is always running on Main thread. I never tried DispatchQueue.main.sync { //some code }
though, until one day when I felt more curiouser than ever. I simply kept DispatchQueue.main.sync { //some code }
inside a function and invoked that function. Just like one below,
func random() {
print("some code")
DispatchQueue.main.sync {
print("UI update")
}
print("finishing code")
}//call random func
random()
And it did crash the application with
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
Then it occurred to me the lame thing I was trying to do. By default, main
thread pitch in and starts running. So, the function random()
will be executed by main
thread. And within the function, I asked the running thread (main thread
) to go to blocking mode until when the block of code within is executed by main
thread. So basically, I asked the main
thread goto blocking mode and, at the same time to execute a code block. DEADLOCK.
Never call
DispatchQueue.main.sync{}
in main thread. It’s a bad idea. Also, doesn’t work.
What is the use case of main.sync { }
I wondered, will there ever be a use case for DispatchQueue.main.sync { }
? Actually there are many. One time I ran into a feature where, I want to fetch a large file from disk, process it and display, this will take significant CPU time. So, I intend to run the task in a concurrent thread and all good. But also, I didn’t want the user to staring at spinner for so long that he hates using my app. So wanted to keep updating the user when a significant work is done. Then I remembered, I could make use of main.sync { }
. Something similar to below snippet.
func processData() {
//do something
DispatchQueue.global(qos: .someQos).async {
//read the file (takes significant time to complete)
DispachQueue.main.sync {
//Update UI[File read was successful]
} //process the file (e.g. decode into a dictionary)
DispatchQueue.main.sync {
//Update UI [Processing successful]
}
//do some more work here
}
}
Final thoughts:
If you find the information useful, do share and don’t forget to give a clap. And drop a suggestion. Thanks for reading.