Why await on the main thread does not cause deadlocks?

    func test() async -> UIImage {
        await withCheckedContinuation { continuation in
            DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
                DispatchQueue.main.async {
                    print("3")
                    continuation.resume(returning: UIImage())
                }
            }

        }
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        Task {
            print("1")
            let _ = await test()
            print("2")
            print("")
        }
}

In my opinion, await will block the main thread, so why the block in which print("3") at can be executed?

1 Like

I suggest you review Concurrency — The Swift Programming Language (Swift 5.7), it should answer your questions. In short, awaits are suspension points which allow code to continue executing elsewhere. Combined with your use of Task, which puts its content onto a separate line of execution in the same context, and there's nothing to block at all.

1 Like

Thanks, I will try to read the guide.

For now, I thinks the code above should work like this

        DispatchQueue.main.async {
            DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
                DispatchQueue.main.async {
                    semaphore.signal()
                    print("2")
                }
            }
            print("init")
            semaphore.wait()
            print("1")
            print("")
        }

semaphore.signal() will not be executed, the main thread will be blocked, what is the key difference between these two snippets?

semaphore.wait() is blocking, whereas await is not.

2 Likes

what is the key difference between these two snippets?

To expand on patrickgoley’s reply, async/await and Dispatch are very different things:

  • When you wait on a semaphore, that translates to a call to the Dispatch C API, dispatch_semaphore_wait, that blocks the current thread until unblocked by the semaphore send.

  • Async functions are run by a small pool of cooperative threads. When you await in an async function, you don’t block that thread. Rather, the Swift compiler rewrites your code to save its state in memory associated with the task and returns control to the runtime. That thread is then free to continue doing other work. When the async function that you’re waiting on completes, the runtime finds a thread to continue execution of your function, restores its saved state from the task, and away you go.

This async/await model is not unique to Swift, but rather a pattern thats been rolling out across the entire industry. For the Swift-specific details, follow the link that
Jon_Shier post. Oh, and if you’re curious about the implementation details, I highly recommend WWDC 2021 Session 10254 Swift concurrency: Behind the scenes.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

8 Likes

Think about Task like DispatchQueue.main.async