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?
CodePudding user response:
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 thanOperation
. In Swift concurrency, we haveTask.sleep
, which, unlikeThread.sleep
, is cancelable and does not block the thread.If you want to stay within
OperationQueue
patterns, you would use an asynchronous customOperation
subclass (perhaps theAsynchronousOperation
shown in either here or here), and then you would use a timer. You could use aDispatchSourceTimer
, or aTimer
, orasyncAfter
with a cancelableDispatchWorkItem
. Which you choose really does not matter. The key is to ensure that thecancel
implementation invalidates theTimer
or cancels theDispatchWorkItem
orDispatchSourceTimer
, 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 checkisCancelled
is inefficient. Instead, set up a timer and implementcancel
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.