【问题标题】:How scalable is System.Threading.Timer?System.Threading.Timer 的可扩展性如何?
【发布时间】:2010-09-07 02:11:11
【问题描述】:

我正在编写一个需要使用Timers 的应用程序,但可能很多。 System.Threading.Timer 类的可扩展性如何?文档只是说它是“轻量级的”,但没有进一步解释。这些计时器是否被吸入代表Timer 处理所有回调的单个线程(或非常小的线程池)中,还是每个Timer 都有自己的线程?

我想另一种表述问题的方式是:System.Threading.Timer 是如何实现的?

【问题讨论】:

    标签: c# .net multithreading timer


    【解决方案1】:

    我这样说是为了回答很多问题:不要忘记框架的(托管)源代码是可用的。您可以使用此工具获取所有信息:http://www.codeplex.com/NetMassDownloader

    不幸的是,在这种特定情况下,很多实现都是在本机代码中实现的,所以你看不到它......

    不过,他们肯定使用池线程而不是每个计时器线程。

    实现大量计时器的标准方法(这是内核在内部执行它的方式,我怀疑间接地是您的大量计时器最终如何结束)是维护按时间到到期排序的列表- 所以系统只需要担心检查即将到期的下一个计时器,而不是整个列表。

    粗略地说,这给了 O(log n) 用于启动计时器和 O(1) 用于处理正在运行的计时器。

    编辑:刚刚在看 Jeff Richter 的书。他说(关于 Threading.Timer)它对所有 Timer 对象使用单个线程,该线程知道下一个计时器(即如上所述)何时到期,并酌情调用 ThreadPool.QueueUserWorkItem 进行回调。这样做的效果是,如果您没有在下一个回调到期之前完成对计时器的一个回调的服务,那么您的回调将重新进入另一个池线程。因此,总而言之,我怀疑拥有大量计时器是否会出现大问题,但如果大量计时器在同一个计时器上触发和/或它们的回调运行缓慢,您可能会遭受线程池耗尽。

    【讨论】:

    • 优先队列可能会比排序列表更有效,除非所有计时器在开始时批量添加,然后排序,以后不再添加。
    • 当然——“一个按时间排序的列表”当然可以是一种优先级队列——我并不是说“一个已经运行过排序操作的列表”
    • 我刚刚花了一些时间查看了 sscli 中的代码。请注意,自 Rotor 发布以来,.NET ThreadPool 发生了巨大变化,因此 System.Threading.Timer 也完全有可能发生了巨大变化。事实上,定时器在 .NET 1.1 中被严重破坏,仅在 .NET 2.0 中被修复为安全安全和异常安全无论如何,在 Rotor 中,定时器保存在一个链接列表中,并由专用的 Timer 触发线程触发。整个运行时(甚至跨多个应用程序域)都有一个计时器触发线程。
    • 线程只是遍历计时器列表并为所有已过期的计时器调用(本机)QueueUserWorkItem。插入新定时器和删除旧定时器的操作在 APC 调用中执行到定时器线程 (msdn.microsoft.com/en-us/library/windows/desktop/…) 总而言之,该实现被设计为简单和安全(现在),但性能不高。也就是说,对于大多数情况,它应该足够快,但如果你试图每秒创建 10000 个计时器,你可能会遇到麻烦。
    • 实际上在做了一些测试之后,似乎计时器遍历仍然是 O(log n),但即使如此,我仍然能够让计时器以每秒约 20,000 个速度在我的机器上保持同步。跨度>
    【解决方案2】:

    我认为您可能需要重新考虑您的设计(也就是说,如果您可以自己控制设计)。如果您使用的计时器太多以至于这实际上是您关心的问题,那么显然有一些整合的潜力。

    这是几年前来自 MSDN 杂志的一篇好文章,比较了三个可用的计时器类,并对它们的实现提供了一些见解:

    http://msdn.microsoft.com/en-us/magazine/cc164015.aspx

    【讨论】:

      【解决方案3】:

      合并它们。创建一个计时器 服务并要求计时器。 它只需要保持 1 处于活动状态 计时器(用于下一次到期调用)...

      要使这比仅仅创建大量 Threading.Timer 对象有所改进,您必须假设这并不是 Threading.Timer 内部已经在做的事情。我很想知道你是如何得出这个结论的(我没有反汇编框架的原生部分,所以你很可能是对的)。

      【讨论】:

        【解决方案4】:

        ^^ 正如 DannySmurf 所说:合并它们。创建一个计时器服务并询问计时器。它只需要保留 1 个活动计时器(用于下一次到期调用)和所有计时器请求的历史记录,并在 AddTimer() / RemoveTimer() 上重新计算。

        【讨论】:

          猜你喜欢
          • 2023-03-17
          • 2012-03-06
          • 1970-01-01
          • 1970-01-01
          • 2018-02-28
          • 2010-09-17
          • 2015-04-04
          • 2012-12-23
          • 2010-11-12
          相关资源
          最近更新 更多