DispatchSourceTimer、Timer和asyncAfter的区别是什么?

61
我很难理解DispatchSourceTimer, TimerasyncAfter之间的主要区别(在我的情况下,需要每X秒运行一次任务,尽管了解计时器的差异可能很有用)。
(或者除了上述计时器之外,Swift中是否有其他(更有效的)调度机制?)

Timer需要在当前队列上启动活动运行循环。 DispatchSourceTimer不需要这样做。Timer防止CPU进入空闲状态。 DispatchSourceTimer/asyncAfter是否也适用于此?

在何种情况下,Timer优先于DispatchSourceTimer/asyncAfter?当然还有它们之间的区别是什么?
我想在我的应用程序中每15秒安排一次工作,使用私有队列。这意味着我必须使用DispatchSourceTimer,因为我在不是主线程的队列上(或者将runloop添加到队列并使用Timer)。然而,我甚至没有看到使用Timer的任何好处。也许有另一种操作,可以在私有队列上每X秒安排工作,比DispatchSourceTimer更有效率,但我没有找到更好的解决方案。
DispatchSourceTimer是否比Timer更有效率?还是我应该使用asyncAfter进行自调用方法?
以下是创建计时器的代码。
asyncAfter
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(2)) {
    // Code
}

计时器

Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (_) in
    // Code
}

DispatchSourceTimer

let timer = DispatchSource.makeTimerSource()

timer.schedule(deadline: .now() + .seconds(1))

timer.setEventHandler {
    // Code
}

timer.activate()

所有计时器的优缺点是什么?在什么情况下应该使用其中之一?哪种计时器方式最有效?我总结了以下内容:

计时器

优点:

  • 可以被无效化
  • 不需要引用
  • 可以在预定时间停止

缺点:

  • 阻止CPU进入空闲状态
  • 需要在带有运行循环的队列上运行(否则什么也不会发生,甚至没有断言触发...)

DispatchSourceTimer

优点:

  • 可以被取消
  • 不需要运行循环

缺点:

  • 需要强引用,否则会立即被释放

asyncAfter

优点: - 不需要运行循环

缺点: - 无法取消(我认为)

还有其他计时器吗?为什么有这么多计时器?我期望在所有不同的计时器中看到一些真正的差异,但我找不到它们。

如您所读,有很多问题。主要问题是:有哪些计时器可用,我应该在什么情况下使用哪些计时器,为什么?


请尝试此链接 https://medium.com/@danielemargutti/the-secret-world-of-nstimer-708f508c9eb,其中非常好地解释了差异。 - user3441734
@user3441734 这是一篇有趣的文章,但我仍然不明白何时使用其中之一。 - J. Doe
1个回答

90

Timer是NSTimer的一个Swift桥接器,它可以追溯到NeXTSTEP时代,甚至比Grand Central Dispatch(GCD)和DispatchSourceTimer等功能更早。这些功能在10.6之前都没有出现(以dispatch_source_set_timer的形式)和dispatchAfter(以dispatch_after的形式)。

NSTimer基于运行循环,这是直到GCD出现之前并发处理的主要方式。它是一种协作并发系统,主要设计用于单线程单核心上运行(虽然它也可以扩展到多线程环境中)。

虽然运行循环在Cocoa中仍然非常重要,但它已不再是管理并发的主要或首选方式。自10.6以来,GCD已成为越来越受欢迎的方法(尽管在10.12时间段内添加基于块的NSTimer API是一个受欢迎的现代化更新)。

在15秒的时间范围内,效率差异相当不相关。话虽如此,我不理解你的评论“计时器使CPU保持不进入空闲状态。”我不相信这是正确的。当等待NSTimer触发时,CPU肯定会进入空闲状态。

我不会创建一个运行循环仅仅为了运行NSTimer。你最好将其安排在主运行循环上,然后使用DispatchQueue.async在其他队列上执行实际的工作。

作为一个基本规则,我会使用满足需求的最高级别工具。苹果可能会随着时间的推移对这些工具进行最佳优化,并且我可以做出最少的更改。例如,NSTimer的触发日期会自动调整以提高能源效率。使用DispatchSourceTimer,您可以控制"leeway"设置以获得同样的好处,但需要您自行设置(默认为零,具有最差的能源影响)。当然,反之亦然。DispatchSourceTimer是最低级别的,可以给您最多的控制权,因此如果您需要它,那就用它。
对于您的示例,我个人可能会使用计时器并将其作为块的一部分派遣到私有队列。但是,DispatchSourceTimer也完全适合。
asyncAfter实际上是一种不同的东西,因为它总是单次触发。如果您只是在块中调用asyncAfter以重复执行,则它将在上一次完成后的15秒后再次执行,而不是每隔15秒执行一次。前者往往会随着时间的推移而稍微偏晚。设计问题是:如果由于某种原因您的任务需要5秒钟才能完成,您希望下一个fire事件在结束后的15秒内发生,还是希望每个fire事件之间保持恒定的15秒间隔?您在这里的选择将决定使用哪个工具。作为一点小提醒,NSTimer事件总是比预定时间晚一些。有一个设置寬限时间的GCD事件可能会早一点或晚一点。实际上,不存在“准时”这样的事情(这是一个零长度的时间段;你不可能达到它)。因此,问题始终是你是否像NSTimer那样被“保证”会迟到,还是像带有寬限时间的GCD一样可能会提前。

那么,如果NSTimer正在使用运行循环,但是该循环已经被GCD淘汰了,那么重复调用代码块的首选高级方法是什么?是否有DispatchSourceTimer的更高级别的对应项? - Eric Wang
2
@EricWang,使用GCD并没有淘汰运行循环,也没有淘汰NSTimer。如果你在ObjC中工作,NSTimer完全适用于它一直擅长的用例。而在Swift中,则是Timer。虽然运行循环不是管理并发的首选方式,但这并不意味着它们已经消失了,与(NS)Timer几乎没有关系,后者仍然是一个完美的工具。 - Rob Napier
1
关于重复使用 asyncAfter,您可以轻松避免时间漂移:在您的 * block * 中,以 asyncAfter 作为第一个操作安排下一次迭代,而不是最后一个操作。另一方面,使用 asyncAfter,不能保证在准确时间调用代码块;DispatchQueue 中此 * func * 的注释为:“在指定时间后将工作项提交给调度队列以进行异步执行”。如果您使用此方法进行实验,您会发现您的代码块实际执行的时间可能比 截止时间 晚10%。 - Alex Cohn
我认为DispatchTimer并没有被定义为能够提前触发,余地是为了在正常情况下安排事件的有限延迟。如果不是的话,我猜我可能把它记录错了,或者是当前的维护者们对其进行了更改,十年间发生了很多事情 :-) 文档中说:“系统可以延迟定时器事件的交付的最长时间。”,所以这似乎是一个“之后”的概念,尽管它并没有承认其他因素可能导致事件被安排得更晚。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接