Sleep in DispatchQueue?

I want to implement this pattern:

let queue = DispatchQueue(label: "maintenance", qos: .utility)
queue.async {
    while true {
        guard let task = GET_PENDING_TASK() else {
            SLEEP(interval)
            continue
        }
        PROCESS(task)
    }
}

Is this a reasonable pattern? If yes, which sleep function should I call? If not, what would be a reasonable pattern to use to accomplish something like this?

I vaguely remember that you are not supposed to sleep in a DispatchQueue, but I think it was on a WWDC video? Is this true? The 6500 lines of generated headers for Dispatch don't seem to mention anything relevant.

You can use DispatchQueue.asyncAfter variations for sleeping behaviour.
You’re not suppose to sleep in DispatchQueue because sleeping over it will prevent other work on the same queue from being executed. Since they don’t expect you to sleep, doing so may have other implication as well.

You can also use DispatchSourceTimer, since it seems you want it to repeat. Do note that you need to keep the timer alive. Further, you can specify the DispatchQueue the timer will operate on.

1 Like

Thanks, I kind of remembered that, but there is a lot of lore around Dispatch spread between videos, mailing list archives and forum posts. Thanks for the verification.

I'm currently using Timers to approximate this, but I was looking for a more straightforward solution. I guess the answer is "This is not a reasonable pattern for Dispatch"?

Looking at your code again I see that you’re actually trying to

  1. Synchronously perform tasks if there’s any and
  2. Sleep until new task arrives

Wouldn’t that already be what DispatchQueue has been doing? Is there some other hidden requirement that I miss?

let queue = DispatchQueue(label: "maintenance", qos: .utility)

func submit(task: TaskType) {
    queue.async {
        PROCESS(task)
    }
}

If the number of tasks is overwhelming, I would suggest using DispatchSemphore and fallback to Producer/Consumer pattern.

There is nobody submitting tasks in my scenario. Imagine for example a background queue that scans cache files and deletes them if they are older than X hours.

As long as there are expired files it should delete them, if there are none, it sleeps and tries again later.

In such case, it wouldn’t be a problem to sleep if nobody is using the queue (yet again, I’m unsure of the performance implication; we could wait patiently for someone in the knows to passby :-).

Becareful in a setup with a hierarchy of queue though, it’ll block all the way up to the root queue. This is also part of the reason I prefer async or timer over sleep in queue, sleep’s more prone to error when queue structure change.

Note: I do personally prefer DispatchSourceTimer over NSTimer, for the actual difference I left it to the glorious google search. One important difference I recall is that DispatchSourceTimer uses DispatchQueue while NSTimer uses Runloop so you may have preference if the system already use one of those.

This is from a few years ago, but I once had problems in a codebase that had many independent concurrent queues where submitted blocks (the number of which depended on user data) waited on semaphores (we have since moved on to a more sensible design). This led to the worker threads Dispatch manages internally being blocked, to which Dispatch would respond by spawning more threads, which eventually led to crashes due to running into thread count limits (iirc, might have been stack space?). The same thing might happen when submitting many blocking work items to a single concurrent queue, I imagine.

Either way, be careful about blocking. If you have a fixed number of queues/work items, it should probably be fine, but you still need to think about what you are doing with your resources.

On macOS, I imagine this to be a launchd job with a StartInterval or WatchPaths key.

I guess the answer is "This is not a reasonable pattern for Dispatch"?

Correct. ahti explains this but I want to go into a little more detail.

Dispatch is responsible for mapping work on queues to worker threads. The goal is to have one thread put core. However, if a thread blocks then Dispatch may need to spawn extra worker threads to pick up the slack while the thread is blocked. Dispatch has a bunch of logic to handle this but in the worst case scenario you can experience a thread explosion.

If you want more info on this, watch WWDC 2015 Session 718 Building Responsive and Efficient Apps with GCD. If you want an outline of current Dispatch best practices, watch WWDC 2017 Session 706 Modernizing Grand Central Dispatch Usage.

In your case the best option would be to use the Thread API, so that your thread does not tie up a Dispatch worker thread. That API is part of Swift’s Foundation because it’s still useful. And with the block syntax introduced in the 2016 OS releases, it’s no harder to use than Dispatch.

Thread {
    while true {
        guard let task = GET_PENDING_TASK() else {
            SLEEP(interval)
            continue
        }
        PROCESS(task)
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes

It feels like for a lot of Dispatch questions the answer is "you are doing it wrong, go watch these videos".

In any case, thanks for the pointer on using the Tread API. That's basically exactly what I want. To be honest I though that the API was discouraged, nice to hear it is still recommended.

1 Like