并发队列上的 DispatchSourceTimer
DispatchSourceTimer on concurrent queue
默认情况下,创建 DispatchSourceTimer
时,默认并发队列用于调度计时器事件和取消。
有趣的是,即使在事件处理程序已经触发后,单发计时器仍会将调用分派给取消处理程序。
所以考虑下面的代码:
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()
}
输出:
event handler
cancel handler
现在根据文档,对 cancel()
的调用应该会阻止事件句柄的执行,但是假设对 cancel()
的调用与对事件处理程序的调用同步是否安全?内部?
Asynchronously cancels the dispatch source, preventing any further invocation of its event handler block.
我想确保其中一个被调用,但不会同时被调用,所以我修改了我的代码并将取消处理程序包装到 DispatchWorkItem
中,我从事件内部取消了它处理程序:
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()
}
但是,我不太确定这段代码是线程安全的。此代码是否可能容易出现竞争条件,其中取消处理程序与事件处理程序同时执行但在相应的 DispatchWorkItem
被取消之前执行?
我意识到我可能会加锁,或者使用串行队列,我的问题是针对熟悉 libdispatch
内部结构的人。
您可能知道,此 libDispatch code is notoriously dense. While it can be edifying to go through it, one should be reluctant to rely upon implementation details, because they are subject to change without warning. One should rely solely upon formal assurances stated in the documentation. Fortunately, setCancelHandler
documentation 提供了这样的保证:
The cancellation handler (if specified) is submitted to the source’s target queue in response to a call to a call to the cancel()
method once the system has released all references to the source’s underlying handle and the source’s event handler block has returned.
因此,为了回答您的 event/cancelation 比赛,文档告诉我们只有在“事件处理程序块返回”后才会调用取消处理程序。
这也可以通过经验来验证,通过明智地插入 sleep
调用来证明潜在的竞争。考虑你的第二个例子的这个演绎:
logger.log("starting timer")
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".timerQueue", attributes: .concurrent)
let timer = DispatchSource.makeTimerSource(queue: queue)
let cancelHandler = DispatchWorkItem {
logger.log("cancel handler")
}
timer.setEventHandler {
logger.log("event handler started")
Thread.sleep(forTimeInterval: 2) // manifest potential race
cancelHandler.cancel()
logger.log("event handler finished")
}
timer.setCancelHandler(handler: cancelHandler)
timer.schedule(wallDeadline: .now() + 1)
timer.activate()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
logger.log("canceling timer")
timer.cancel()
}
logger.log("done starting timer")
这会产生:
2021-10-05 11:29:45.198865-0700 MyApp[18873:4847424] [ViewController] starting timer
2021-10-05 11:29:45.199588-0700 MyApp[18873:4847424] [ViewController] done starting timer
2021-10-05 11:29:46.199725-0700 MyApp[18873:4847502] [ViewController] event handler started
2021-10-05 11:29:47.387352-0700 MyApp[18873:4847424] [ViewController] canceling timer
2021-10-05 11:29:48.204222-0700 MyApp[18873:4847502] [ViewController] event handler finished
注意,没有“取消处理程序”消息。
因此,简而言之,我们可以看到 GCD 解决了事件和取消处理程序之间的这种潜在竞争,如文档中所述。
默认情况下,创建 DispatchSourceTimer
时,默认并发队列用于调度计时器事件和取消。
有趣的是,即使在事件处理程序已经触发后,单发计时器仍会将调用分派给取消处理程序。
所以考虑下面的代码:
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()
}
输出:
event handler
cancel handler
现在根据文档,对 cancel()
的调用应该会阻止事件句柄的执行,但是假设对 cancel()
的调用与对事件处理程序的调用同步是否安全?内部?
Asynchronously cancels the dispatch source, preventing any further invocation of its event handler block.
我想确保其中一个被调用,但不会同时被调用,所以我修改了我的代码并将取消处理程序包装到 DispatchWorkItem
中,我从事件内部取消了它处理程序:
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()
}
但是,我不太确定这段代码是线程安全的。此代码是否可能容易出现竞争条件,其中取消处理程序与事件处理程序同时执行但在相应的 DispatchWorkItem
被取消之前执行?
我意识到我可能会加锁,或者使用串行队列,我的问题是针对熟悉 libdispatch
内部结构的人。
您可能知道,此 libDispatch code is notoriously dense. While it can be edifying to go through it, one should be reluctant to rely upon implementation details, because they are subject to change without warning. One should rely solely upon formal assurances stated in the documentation. Fortunately, setCancelHandler
documentation 提供了这样的保证:
The cancellation handler (if specified) is submitted to the source’s target queue in response to a call to a call to the
cancel()
method once the system has released all references to the source’s underlying handle and the source’s event handler block has returned.
因此,为了回答您的 event/cancelation 比赛,文档告诉我们只有在“事件处理程序块返回”后才会调用取消处理程序。
这也可以通过经验来验证,通过明智地插入 sleep
调用来证明潜在的竞争。考虑你的第二个例子的这个演绎:
logger.log("starting timer")
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".timerQueue", attributes: .concurrent)
let timer = DispatchSource.makeTimerSource(queue: queue)
let cancelHandler = DispatchWorkItem {
logger.log("cancel handler")
}
timer.setEventHandler {
logger.log("event handler started")
Thread.sleep(forTimeInterval: 2) // manifest potential race
cancelHandler.cancel()
logger.log("event handler finished")
}
timer.setCancelHandler(handler: cancelHandler)
timer.schedule(wallDeadline: .now() + 1)
timer.activate()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
logger.log("canceling timer")
timer.cancel()
}
logger.log("done starting timer")
这会产生:
2021-10-05 11:29:45.198865-0700 MyApp[18873:4847424] [ViewController] starting timer
2021-10-05 11:29:45.199588-0700 MyApp[18873:4847424] [ViewController] done starting timer
2021-10-05 11:29:46.199725-0700 MyApp[18873:4847502] [ViewController] event handler started
2021-10-05 11:29:47.387352-0700 MyApp[18873:4847424] [ViewController] canceling timer
2021-10-05 11:29:48.204222-0700 MyApp[18873:4847502] [ViewController] event handler finished
注意,没有“取消处理程序”消息。
因此,简而言之,我们可以看到 GCD 解决了事件和取消处理程序之间的这种潜在竞争,如文档中所述。