By default when creating a DispatchSourceTimer
, the default concurrent queue is used for dispatching timer events and cancellation.
What's interesting that one shot timers still dispatch the call to cancellation handler even after the event handler has already fired.
So consider the following code:
let timer = DispatchSource.makeTimerSource()
timer.setEventHandler {
print("event handler")
}
timer.setCancelHandler {
print("cancel handler")
}
timer.schedule(wallDeadline: .now())
timer.activate()
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
timer.cancel()
}
Output:
event handler
cancel handler
Now based on documentation the call to cancel()
is supposed to prevent the event handle from executing, however is it safe to assume that the call to cancel()
is synchronised with the call to event handler internally?
Asynchronously cancels the dispatch source, preventing any further invocation of its event handler block.
I'd like to make sure that either one or the other is called but not both, so I modified my code and wrapped the cancel handler into DispatchWorkItem
, which I cancel from the inside of the event handler:
let timer = DispatchSource.makeTimerSource()
var cancelHandler = DispatchWorkItem {
print("cancel handler")
}
timer.setEventHandler {
cancelHandler.cancel()
print("event handler")
}
timer.setCancelHandler(handler: cancelHandler)
timer.schedule(wallDeadline: .now())
timer.activate()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
timer.cancel()
}
However, I am not quite sure this code is thread safe. Is this code potentially prone to race condition where cancel handler executes simultaneously with event handler but before the corresponding DispatchWorkItem
is cancelled?
I realise that I can probably add locks around, or use a serial queue, my question is to folks familiar with libdispatch
internals.