dispatch_async
dispatch_sync
serial queue
concurrency
Swift programming

Difference between dispatch_async and dispatch_sync on serial queue?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

On a serial queue, both dispatch_async and dispatch_sync submit work in order, but they differ in what the calling thread does next. dispatch_async schedules the work and returns immediately. dispatch_sync schedules the work and blocks the caller until that work finishes. That difference becomes critical on serial queues because dispatch_sync to the same queue can deadlock.

Serial Queues Run One Block at a Time

A serial queue guarantees ordered execution. If you submit three blocks, they run one after another, never at the same time.

In modern Swift, you usually write:

swift
let queue = DispatchQueue(label: "com.example.serial")

That queue processes tasks sequentially regardless of whether you use async or sync.

async Enqueues and Returns Immediately

With async, the block is added to the queue, and the caller continues without waiting.

swift
1let queue = DispatchQueue(label: "com.example.serial")
2
3print("before async")
4
5queue.async {
6    print("running on serial queue")
7}
8
9print("after async")

The important behavior is:

  • the block will run later in queue order
  • the current thread does not wait for completion

This is the right choice when you want ordered work but do not need the result immediately.

sync Enqueues and Blocks the Caller

With sync, the calling thread waits until the submitted block has finished.

swift
1let queue = DispatchQueue(label: "com.example.serial")
2
3print("before sync")
4
5queue.sync {
6    print("running synchronously on serial queue")
7}
8
9print("after sync")

Here, after sync is printed only after the block completes. The queue is still serial, but now the caller is blocked during the work.

Why sync Can Deadlock on the Same Serial Queue

The classic failure case is calling sync from code that is already running on that same serial queue.

swift
1let queue = DispatchQueue(label: "com.example.serial")
2
3queue.async {
4    print("outer start")
5
6    queue.sync {
7        print("inner")
8    }
9
10    print("outer end")
11}

This deadlocks. The outer block is already occupying the serial queue. The inner sync call waits for the queued block to run, but the queue cannot start that inner block until the outer block finishes. Neither side can progress.

That is the most important practical difference to remember.

Ordered Execution Does Not Mean the Same Calling Semantics

On a serial queue, both methods preserve order. The difference is not ordering. The difference is whether the caller waits.

Use this mental model:

  • 'async: "do this later on that queue"'
  • 'sync: "do this on that queue and do not let me continue until it is done"'

That distinction affects responsiveness, deadlock risk, and how you structure dependent work.

Typical Use Cases

async on a serial queue is common for:

  • protecting shared mutable state without blocking the caller longer than needed
  • logging or file writes that should stay ordered
  • background serialization of work

sync on a serial queue is appropriate when:

  • you need a value immediately
  • you are calling from outside that queue
  • you know blocking the caller is acceptable

For example:

swift
1final class Counter {
2    private let queue = DispatchQueue(label: "counter.queue")
3    private var value = 0
4
5    func increment() {
6        queue.async {
7            self.value += 1
8        }
9    }
10
11    func currentValue() -> Int {
12        queue.sync {
13            value
14        }
15    }
16}

This pattern uses async for mutation and sync for a safe read from callers outside the queue.

Main Queue Considerations

Using DispatchQueue.main.sync from the main thread is another deadlock pattern. The main thread is already executing the current code path, so synchronously dispatching back onto it waits forever.

That is why UI work is usually scheduled with:

swift
DispatchQueue.main.async {
    // update UI
}

not with sync from the main thread.

Common Pitfalls

The biggest mistake is calling sync on the same serial queue you are already running on. Another is assuming sync is "faster" because it starts immediately; the real issue is whether blocking is acceptable. Developers also confuse ordered execution with thread identity. A serial queue guarantees one-at-a-time work, not that the same physical thread is always used. In UI code, DispatchQueue.main.sync from the main thread is a classic deadlock trap.

Summary

  • Both async and sync preserve order on a serial queue.
  • 'async returns immediately and does not block the caller.'
  • 'sync blocks the caller until the submitted work finishes.'
  • 'sync on the same serial queue causes deadlock.'
  • Use async for queued work that does not need an immediate result.
  • Use sync only when you need completion before continuing and you are outside that queue.

Course illustration
Course illustration

All Rights Reserved.