How to interrupt Thread.sleep. Alternatives?

577 views Asked by At

I have implemented an Operation on an OperationQueue.

override func main() {
    super.main()
    if isCancelled {
        return
    }
    if member.memberType == .timed {
        triggerRestEvent(duration: member.restDuration)
    }
    if isCancelled {
        triggerEndEvent()
    }
}

The triggerRestEvent function is actually calling Thread.sleep. Once the sleep has expired we then check isCancelled.

Is there a way to interrupt Thread.sleep when isCancelled is toggled on?

Alternative - RunLoop

The docs for RunLoop suggest a while loop around the function run with a custom condition in the while loop. But how would I setup a timer to toggle the while loops execution? And apparently using a while loop in this way, for this purpose, is an antipattern these days?

1

There are 1 answers

0
Rob On BEST ANSWER

Thread.sleep is non-cancelable and blocks a thread. And spinning on a RunLoop is inefficient. That having been said, there are a few alternatives:

  1. Nowadays, to manage dependencies between asynchronous tasks, we would reach for Swift concurrency’s Task rather than Operation. In Swift concurrency, we have Task.sleep, which, unlike Thread.sleep, is cancelable and does not block the thread.

  2. If you want to stay within OperationQueue patterns, you would use an asynchronous custom Operation subclass (perhaps the AsynchronousOperation shown in either here or here), and then you would use a timer. You could use a DispatchSourceTimer, or a Timer, or asyncAfter with a cancelable DispatchWorkItem. Which you choose really does not matter. The key is to ensure that the cancel implementation invalidates the Timer or cancels the DispatchWorkItem or DispatchSourceTimer, e.g.:

    class OneSecondOperation: AsynchronousOperation {
        weak var timer: Timer?
    
        override func main() {
            DispatchQueue.main.async {
                self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { [weak self] _ in
                    self?.finish()
                }
            }
        }
    
        override func cancel() {
            super.cancel()
            timer?.invalidate()
            finish()
        }
    }
    

    Note, the pattern whereby you periodically check isCancelled only applies if you have an existing loop. E.g., if you are doing some iterative calculation, for example, that is a very reasonable pattern. But if you are just waiting, the idea of introducing a loop merely to check isCancelled is inefficient. Instead, set up a timer and implement cancel method that cancels that timer, as shown above.

Either way, you want implementation that does not block a thread and can be canceled. With Operation subclass you have to implement that yourself. With Swift concurrency, you get that for free.