Using DispatchSemaphore to make synchronous calls in Swift

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:

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *