System.Threading.Timer可伸缩的吗?

时间:2022-07-21 20:38:11

I'm writing an app that will need to make use of Timers, but potentially very many of them. How scalable is the System.Threading.Timer class? The documentation merely say it's "lightweight", but doesn't explain further. Do these timers get sucked into a single thread (or very small threadpool) that processes all the callbacks on behalf of a Timer, or does each Timer have its own thread?

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

I guess another way to rephrase the question is: How is System.Threading.Timer implemented?

我猜另一种表达这个问题的方式是:System.Threading。定时器实现?

4 个解决方案

#1


29  

I say this in response to a lot of questions: Don't forget that the (managed) source code to the framework is available. You can use this tool to get it all: http://www.codeplex.com/NetMassDownloader

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

Unfortunately, in this specific case, a lot of the implementation is in native code, so you don't get to look at it...

不幸的是,在这个特定的情况下,很多实现都是本地代码,所以您无法查看它……

They definitely use pool threads rather than a thread-per-timer, though.

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

The standard way to implement a big collection of timers (which is how the kernel does it internally, and I would suspect is indirectly how your big collection of Timers ends up) is to maintain the list sorted by time-until-expiry - so the system only ever has to worry about checking the next timer which is going to expire, not the whole list.

计时器的标准方法来实现一个大集合(内部内核如何呢,我怀疑是间接你大的计时器结束)是维护列表按time-until-expiry——所以系统只担心检查下一个定时器到期,不是整个列表。

Roughly, this gives O(log n) for starting a timer and O(1) for processing running timers.

大致来说,这给出O(log n)用于启动计时器,O(1)用于处理运行计时器。

Edit: Just been looking in Jeff Richter's book. He says (of Threading.Timer) that it uses a single thread for all Timer objects, this thread knows when the next timer (i.e. as above) is due and calls ThreadPool.QueueUserWorkItem for the callbacks as appropriate. This has the effect that if you don't finish servicing one callback on a timer before the next is due, that your callback will reenter on another pool thread. So in summary I doubt you'll see a big problem with having lots of timers, but you might suffer thread pool exhaustion if large numbers of them are firing at the same timer and/or their callbacks are slow-running.

编辑:刚刚看了杰夫·里希特的书。他说(对thread .Timer)它为所有计时器对象使用一个线程,这个线程知道下一个计时器(也就是上面的时间)是否到期,并调用ThreadPool。QueueUserWorkItem用于适当的回调。这就产生了这样的效果,如果在下次到期之前没有完成对一个回调的服务,那么回调将重新进入另一个池线程。因此,总之,我怀疑您是否会看到大量计时器的大问题,但是如果大量计时器和/或它们的回调运行缓慢,您可能会遇到线程池耗尽的问题。

#2


7  

I think you might want to rethink your design (that is, if you have control over the design yourself). If you're using so many timers that this is actually a concern for you, there's clearly some potential for consolidation there.

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

Here's a good article from MSDN Magazine from a few years ago that compares the three available timer classes, and gives some insight into their implementations:

这是几年前MSDN杂志上的一篇很好的文章,对比了三个可用的计时器类,并对它们的实现给出了一些见解:

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

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

#3


5  

Consolidate them. Create a timer service and ask that for the timers. It will only need to keep 1 active timer (for the next due call)...

巩固它们。创建一个计时器服务,并请求计时器。它只需要保持一个活动计时器(下次到期)……

For this to be an improvement over just creating lots of Threading.Timer objects, you have to assume that it isn't exactly what Threading.Timer is already doing internally. I'd be interested to know how you came to that conclusion (I haven't disassembled the native bits of the framework, so you could well be right).

与创建大量线程相比,这是一个改进。定时器对象,你必须假设它不完全是线程。Timer已经在内部运行了。我很想知道你是如何得出这个结论的(我还没有分解框架的原生部分,所以你很可能是对的)。

#4


0  

^^ as DannySmurf says : Consolidate them. Create a timer service and ask that for the timers. It will only need to keep 1 active timer (for the next due call) and a history of all the timer requests and recalculate this on AddTimer() / RemoveTimer().

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

#1


29  

I say this in response to a lot of questions: Don't forget that the (managed) source code to the framework is available. You can use this tool to get it all: http://www.codeplex.com/NetMassDownloader

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

Unfortunately, in this specific case, a lot of the implementation is in native code, so you don't get to look at it...

不幸的是,在这个特定的情况下,很多实现都是本地代码,所以您无法查看它……

They definitely use pool threads rather than a thread-per-timer, though.

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

The standard way to implement a big collection of timers (which is how the kernel does it internally, and I would suspect is indirectly how your big collection of Timers ends up) is to maintain the list sorted by time-until-expiry - so the system only ever has to worry about checking the next timer which is going to expire, not the whole list.

计时器的标准方法来实现一个大集合(内部内核如何呢,我怀疑是间接你大的计时器结束)是维护列表按time-until-expiry——所以系统只担心检查下一个定时器到期,不是整个列表。

Roughly, this gives O(log n) for starting a timer and O(1) for processing running timers.

大致来说,这给出O(log n)用于启动计时器,O(1)用于处理运行计时器。

Edit: Just been looking in Jeff Richter's book. He says (of Threading.Timer) that it uses a single thread for all Timer objects, this thread knows when the next timer (i.e. as above) is due and calls ThreadPool.QueueUserWorkItem for the callbacks as appropriate. This has the effect that if you don't finish servicing one callback on a timer before the next is due, that your callback will reenter on another pool thread. So in summary I doubt you'll see a big problem with having lots of timers, but you might suffer thread pool exhaustion if large numbers of them are firing at the same timer and/or their callbacks are slow-running.

编辑:刚刚看了杰夫·里希特的书。他说(对thread .Timer)它为所有计时器对象使用一个线程,这个线程知道下一个计时器(也就是上面的时间)是否到期,并调用ThreadPool。QueueUserWorkItem用于适当的回调。这就产生了这样的效果,如果在下次到期之前没有完成对一个回调的服务,那么回调将重新进入另一个池线程。因此,总之,我怀疑您是否会看到大量计时器的大问题,但是如果大量计时器和/或它们的回调运行缓慢,您可能会遇到线程池耗尽的问题。

#2


7  

I think you might want to rethink your design (that is, if you have control over the design yourself). If you're using so many timers that this is actually a concern for you, there's clearly some potential for consolidation there.

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

Here's a good article from MSDN Magazine from a few years ago that compares the three available timer classes, and gives some insight into their implementations:

这是几年前MSDN杂志上的一篇很好的文章,对比了三个可用的计时器类,并对它们的实现给出了一些见解:

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

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

#3


5  

Consolidate them. Create a timer service and ask that for the timers. It will only need to keep 1 active timer (for the next due call)...

巩固它们。创建一个计时器服务,并请求计时器。它只需要保持一个活动计时器(下次到期)……

For this to be an improvement over just creating lots of Threading.Timer objects, you have to assume that it isn't exactly what Threading.Timer is already doing internally. I'd be interested to know how you came to that conclusion (I haven't disassembled the native bits of the framework, so you could well be right).

与创建大量线程相比,这是一个改进。定时器对象,你必须假设它不完全是线程。Timer已经在内部运行了。我很想知道你是如何得出这个结论的(我还没有分解框架的原生部分,所以你很可能是对的)。

#4


0  

^^ as DannySmurf says : Consolidate them. Create a timer service and ask that for the timers. It will only need to keep 1 active timer (for the next due call) and a history of all the timer requests and recalculate this on AddTimer() / RemoveTimer().

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