为每个实例创建计时器有什么问题吗?

本文关键字:什么 问题 计时器 实例 创建 | 更新日期: 2023-09-27 17:51:00

为了更好地理解,我将以DHCP租期的简单抽象为例:租期包含IP地址和MAC地址,租期被授予的时间,并且可以在给定的时间范围内更新。一旦过期,将调用一个事件。同样,这只是我能想到的最简单的例子:

using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Timers;
namespace Example
{
    public class Lease
    {
        public IPAddress IP
        {
            get;
            private set;
        }
        public PhysicalAddress MAC
        {
            get;
            private set;
        }
        public DateTime Granted
        {
            get;
            private set;
        }
        public event EventHandler Expired;
        private readonly Timer timer;
        public Lease(IPAddress ip, PhysicalAddress mac, TimeSpan available)
        {
            IP = ip;
            MAC = mac;
            timer = new Timer();
            timer.AutoReset = false;
            timer.Elapsed += timerElapsed;
            Renew(available);
        }
        public void timerElapsed(object sender, EventArgs e)
        {
            var handle = Expired;
            if (handle != null)
            {
                handle(this, EventArgs.Empty);
            }
        }
        public void Renew(TimeSpan available)
        {
            Granted = DateTime.Now;
            timer.Interval = available.TotalMilliseconds;
            timer.Enabled = true;
        }
    }
}

在创建——例如——"几千个"这样一个类的实例时,有什么需要考虑的吗?我最担心的是计时器。对于这样的任务,我是否应该考虑另一种设计模式(比如所有租约的管理器,或者根本不使用计时器?)或者在创建大量计时器时没有什么可担心的,这是合适的方式?至少当涉及到计时器和事件时,我总是尽量保持谨慎。

为每个实例创建计时器有什么问题吗?

无需创建数千个计时器,只需存储每个Lease对象的过期时间,然后在单个线程中定期查询过期的对象。

一个我头脑中的代码示例:

var leases = new List<Lease>();
var running = true;
var expiredChecker = Task.Factory.StartNew(() =>
{
    while (running)
    {
        var expired = leases.All(l => l.ExpirationDate < DateTime.Now);
        // do something with the expired lease objects
    }
});

假设你有一个IEnumerable<Lease>,一个DateTime属性称为ExpirationDate在你的Lease对象,然后你可以取消它通过设置运行为false当你想要停止。

我认为这部分取决于服务器上可用的资源,以及需要什么样的准确性和性能。

另一种方法可能是在每个实例中存储时间戳这样简单的东西,并定期检查该值,将其与当前时间进行比较,并适当地更新它。我有一种预感,这个可能在性能上更容易-但您应该尝试以某种方式对其进行基准测试以确保。

当然,如果你有大量的实例,遍历它们也可能需要一些时间,所以也许可以将它们池成组,其中每个组在一个单独的线程中定期(可调?)间隔处理可能是一个选项。

如果没有一些关于性能的信息,在这里很难给出一个很好的答案,所以你可能应该只是创建一个概念证明,并测试一些你认为可能有效的策略,并尝试对它们进行基准测试,看看哪一个最适合。

根据System.Timers.Timer MSDN页面:

基于服务器的Timer被设计为与工作线程一起使用多线程环境。服务器计时器可以在线程之间移动到处理引发的Elapsed事件,从而获得比Windows计时器在触发事件的时间。

这意味着当您同时运行数千个计时器时,它不太可能引起问题。

这并不意味着这是一个好方法,您可能应该寻找一个更集中的解决方案来解决这个问题。

我建议使用System.Threading.Timer而不是System.Timers.Timer。第二个是关于第一个在设计时可见的包装,如果你真的不需要设计时支持,它是不必要的。Timer内部调用ThreadPool。QueueUseWorkItem,那么线程池负责维护线程的计时器tick。线程池只使用一个线程来维护所有的计时器对象,该线程决定每个计时器队列上的新线程何时在计时器上滴答。

我不能看到任何开销,除非你的计时器将滴答如此之快,你不能做所有的滴答工作,你只是在线程池中排队太多的工作。