Modern-day CPUs are capable of running multiple execution threads simultaneously. This capability is called multi-threading and allows executing multiple tasks at the same time.
In programming, this capability is closely related to what is called synchronous (sync) calls and asynchronous (async) calls. A synchronous call will block the execution of the thread until the call is completed.
An asynchronous call, on the other hand, will not block the current thread because it will be executed in a different thread.
Nowadays, many API calls are asynchronous. When the call completes, the result is passed to the calling thread. That’s the current state of things, and we’re happy and grateful for that.
Nevertheless there are some cases where you might want to wait, that is, block execution, until a number of async calls complete. To accomplish this, you can use a semaphore. The following playground code shows how:
import UIKit func synchronousTask(taskID : Int) { let semaphore = DispatchSemaphore(value: 0) // see note below print("Waiting for task \(taskID) to complete") // dispatch work to a different thread DispatchQueue.global(qos: .background).async { print("Starting task \(taskID)") sleep(3) // simulate the duration of the task print("Ending task \(taskID)") semaphore.signal() } semaphore.wait() // block until the task completes print("Task \(taskID) is completed") } // semaphores shouldn't be used on the main thread DispatchQueue.global(qos: .background).async { synchronousTask(taskID: 1) }
Value used for semaphore initialization.
The value used in the DispatchSemaphore initialization is related to its purpose. According to Apple: “Passing zero for the value is useful for when two threads need to reconcile the completion of a particular event.”
Since this is what we want, we initialize the semaphore with a value of zero.
The above code produces the following output:
Waiting for more than one task to complete.
If you need to wait for the completion of more than 1 task, simply write a wrapper function to call the synchronous code, as shown in the following code:
import UIKit func synchronousTask(taskID : Int) { let semaphore = DispatchSemaphore(value: 0) print("Waiting for task \(taskID) to complete") // dispatch work to a different thread DispatchQueue.global(qos: .background).async { print("Starting task \(taskID)") sleep(1) // simulate the duration of the task print("Ending task \(taskID)") semaphore.signal() } semaphore.wait() // block until the task completes print("Task \(taskID) is completed") } func synchronousCall() { print("Waiting for tasks to be executed") for i in 1...3 { synchronousTask(taskID: i) } print("All tasks were executed") } // semaphores shouldn't be used on the main thread DispatchQueue.global(qos: .background).async { synchronousCall() }
The above code produces the following output: