Delay and chain operations with DispatchQueue

Jun 3, 2020 · Follow on Twitter and Mastodon swiftasync-await

In this post, we’ll look at how DispatchQueue can be used to delay and chain operations. We’ll also extend it with convenient functions that simplify these tasks.

Delaying operations

Delaying operations with DispatchQueue is very easy, using asyncAfter:

let queue = DispatchQueue.main
let delay = DispatchTimeInterval.milliseconds(50)
queue.asyncAfter(deadline: .now() + delay) {
    print("I was delayed 50 milliseconds")
}

However, I think the deadline function is not that convenient to use, since you have to add .now() to your “deadline”. It’s probably correct from how the queue operates, but using it is not that nice.

Let’s create an extension that makes delaying operations easier:

public extension DispatchQueue {
    
    func asyncAfter(
        _ interval: DispatchTimeInterval,
        execute: @escaping () -> Void) {
        asyncAfter(
            deadline: .now() + interval,
            execute: execute)
    }
}

We can now delay operations like this:

let queue = DispatchQueue.main
queue.asyncAfter(.seconds(1)) {
    print("I was delayed 1 second")
}

I think this is much cleaner, although I’d prefer the function to be called performAfter(...) instead. However, I chose this name to harmonize with the existing apis.

Chaining operations

Chaining operations with DispatchQueue is easy as well, using async to perform another operation on the same or another queue:

let queue = DispatchQueue.main
queue.async {
    { print("Hello") }
    queue.async { print(", world!") }
}

If an async operation returns a value, it can be passed to the chained operation like this:

let queue = DispatchQueue.main
queue.async {
    let result = { return "Hello" }
    queue.async { print(result + ", world!") }
}

This works well, but like asyncAfter, I think this is lacking in usability as well.

Let’s create an extension that makes chaining operations easier:

public extension DispatchQueue {
    
    func async<T>(
        execute: @escaping () -> T,
        then completion: @escaping (T) -> Void,
        on completionQueue: DispatchQueue = .main) {
        async {
            let result = execute()
            completionQueue.async {
                completion(result)
            }
        }
    }
}

We can now chain operations like this:

let queue = DispatchQueue.main
queue.async(
    execute: { return "Hello"}, 
    then: { print($0 + ", world!") },
    on: .main
)

If you’re happy with using the default .main completion queue, this can also be expressed as:

let queue = DispatchQueue.main
queue.async(execute: { return "Hello"}) {
    print($0 + ", world!")
}

As with the delay operations, I’d have preferred to use perform instead of async and will perhaps over time make the functions more compact, but for now I’ve chosen to conform to the existing apis.

Source code

I have added these extensions to my SwiftKit library. You can find the source code here. Feel free to try it out and let me know what you think!

Discussions & More

Please share any ideas, feedback or comments you may have in the Disqus section below, or by replying on Twitter or Mastodon..

If you found this text interesting, make sure to follow me on Twitter and Mastodon for more content like this, and to be notified when new content is published.

If you like & want to support my work, please consider sponsoring me on GitHub Sponsors.