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?
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:
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.
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.