Demystifying DispatchQueue.main.sync { }

What is the real use case of DispatchQueue’s sync { } block

Saravanakumar S
4 min readApr 9, 2021

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 queues DispatchQueue.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.

  1. Main thread prints prior work
  2. Main thread schedules the code block[lines 2 to 4] on global Thread and continues its execution(because of the async keyword)
  3. Main thread prints posterior work
  4. Global thread executes the block and prints some other work

So, async keyword doesn’t tell global thread (the thread on which task is scheduled) to execute the code block [lines 2 to 4] asynchronously, rather it commands the main thread (the thread thats running this whole block) to continue its work, without setting itself in blocked mode till the code block [lines 2 to 4] is executed by userInitiated 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.

  1. Main thread prints prior work
  2. Put the main thread on blocking mode (because of the sync keyword) and start executing the code block [lines 2 to 4] on global thread
  3. Global thread prints some UI work
  4. The main thread gets unblocked as global thread finished execution
  5. 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.

References:

--

--

Saravanakumar S
Saravanakumar S

Written by Saravanakumar S

iOS App developer. Trying to be a better developer than yesterday.

No responses yet