如何每 100 毫秒执行不超过一次的某些类方法?

Posted

技术标签:

【中文标题】如何每 100 毫秒执行不超过一次的某些类方法?【英文标题】:how to execute certain class method no more than once per 100ms? 【发布时间】:2011-04-15 14:32:22 【问题描述】:

我正在编写交易软件,需要对一种方法进行 QoS 处理,该方法的执行频率不应超过每秒 10 次。由于我是 C# 的初学者并且几乎不熟悉库,因此我想仔细检查我的代码是否“最佳”。我正在使用Stopwatch,因为我不知道 C# 中的任何其他计时器。

    Stopwatch updateStopwatch = Stopwatch.StartNew();

    private void update()
    
        if (updateStopwatch.ElapsedMilliseconds < 100)
        
            Console.WriteLine("!skip update " + updateStopwatch.ElapsedMilliseconds);
            return;
         else
        
            Console.WriteLine("!update");
            updateStopwatch.Restart();;
        
        // do work here
    

upd 现在看来,Stopwatch 非常适合这项任务。但是可能它太慢了,如果是这样的话 DateTime 可能会更好。也卖Stopwatch vs. using System.DateTime.Now for timing events

【问题讨论】:

update() 怎么称呼?什么时候触发一些事件?一个按钮被点击?一些计时器触发器? update() 在事件中调用(当市场数据发生变化时)。有几个事件可以调用 update() 【参考方案1】:

您使用Stopwatch 的技术是防止代码更频繁执行的最佳解决方案。正如其他人所说,如果您想确保按计划执行该方法,使用Timer 是一个更好的解决方案。

任何基于DateTime 的方法都从根本上被破坏了,因为它会在日期更改时失败。这在夏令时切换期间尤其明显。当我们“提前”时,更新可能会快速连续运行两次,因为代码认为距离上一次更新已经过去了一个小时。这还不错。但是当我们“回退”时,更新将被暂停整整一个小时,因为最后一次更新时间提前了一个小时。

如果您的计算机设置为从 NTP 服务器定期更新其时间,则可能会发生同样的事情,尽管没有那么严重。如果时间提前,则有可能快速连续发生两次更新。如果时间倒退,则在时钟倒退的时间内可能不会发生更新。

有一些方法可以解决这个问题(例如使用毫秒数的绝对值),但您只是在损坏的解决方案上贴上绷带。您不应该依赖DateTime 来处理这样的时间间隔,因为您的程序无法控制系统时钟——它可以随时更改。

Stopwatch 是这里唯一合理的解决方案,因为它取决于 CPU 的性能计数器,它只会增加。您不会遇到有人设置计数器的问题,也不会遇到像Environment.TickCount 这样的问题会遇到的翻转问题。

有人认为Stopwatch 会导致性能损失,而DateTime 不会。我的测试表明这是不真实的。

【讨论】:

依赖DateTime是什么意思? 我没有说某事取决于DateTime。我说过,如果你写一个依赖DateTime的解决方案,它会失败。【参考方案2】:

秒表和计时器是相当昂贵的对象。您可以简单地将 DateTime 对象作为变量保存并执行比较。

DateTime lastCheck = DateTime.Now;

private void update()

    // DateTime.Subtract returns a TimeSpan
    int elapsed = DateTime.Now.Subtract(lastCheck).Milliseconds;
    if (elapsed < 100)
    
        Console.WriteLine("!skip update " + elapsed.ToString());
        return;
     else
    
        Console.WriteLine("!update");
        lastCheck = DateTime.Now;
    
    // do work here

【讨论】:

DateTime 也相当昂贵。唯一的区别是您使用主线程而不是主线程的子线程来处理它。 @Ramhound:DateTime 并不像计时器或秒表那样昂贵。对于 DateTime 不需要的事件,必须加载和触发大量事件。从功能上讲,DateTime 可能比理想的更昂贵,但仍然可能更容易维护并且更容易实现。无论如何,这只是一个想法,很可能不是最理想的解决方案。 Stopwatch 怎么贵?它所做的只是跟踪开始时间,每次访问Elapsed 属性时,它都会调用QueryPerformanceCounter。根本不涉及“事件”。 你会想要TotalMilliseconds,而不是Milliseconds @Jim Mischel:正确的 TotalMilliseconds 会更合适。我面前没有基准。我读了一篇文章(我现在找不到了),指出计时器和秒表的性能受到影响(计时器是最差的)。我不能权威地说话,所以持保留态度。【参考方案3】:

我不会使用Stopwatch 或任何其他Timer 之类的东西。而是只存储方法调用的时间,如果当前时间和存储时间之间的差异大于 100ms,则只执行后续调用。

您可以实现一个帮助类以更通用的方式执行此操作:

public class TimedGate

     private DateTime m_Last;
     private TimeSpan m_Gap;

     public TimedGate(TimeSpan gap)
     
         m_Gap = gap;
     

     public bool TryEnter()
                 
         DateTime now = DateTime.UtcNow;
         if (now.Subtract(m_Last) > m_Gap)
         
              m_LastEntered = now;   
              return true;                 
         
         return false;  
     

像这样使用它:

TimedGate m_UpdateGate = new TimedGate(TimeSpan.FromMilliseconds(100));

private void Update()

    if (m_UpdateGate.TryEnter())
    
        Console.WriteLine("!update");

        // do work here
    
    else
    
        Console.WriteLine("!skip update");
    

【讨论】:

【参考方案4】:

总是有System.Timer 计时器。

这可能比秒表更容易使用(通常用于测量事情需要多长时间)。

代码:

    var timer = new System.Timers.Timer();

    // Hook up the Elapsed event for the timer using a lambda
    timer.Elapsed += (o, e) => Console.WriteLine("Timer elapsed");

    // Set the Interval to 100 ms
    timer.Interval = 100;

    // Start the timer.
    timer.Enabled = true;

MSDN 文档:http://msdn.microsoft.com/en-us/library/system.timers.timer(v=VS.100).aspx

【讨论】:

秒表基于高分辨率计时器。我不认为 System.Timer 基于高分辨率计时器。秒表可以通过IsHighResolution查看。 无论哪种方式,秒表都是测量时间的,不能用作计时器。并且:“如果安装的硬件和操作系统支持高分辨率性能计数器,则 Stopwatch 类使用该计数器来测量经过的时间。否则,Stopwatch 类使用系统计时器来测量经过的时间。” 其实我不需要计时器,这意味着我不需要每 100 毫秒生成一次事件。我需要防止每 100 毫秒产生一个以上的事件。

以上是关于如何每 100 毫秒执行不超过一次的某些类方法?的主要内容,如果未能解决你的问题,请参考以下文章

在Java中如何实现较为精确的定时器

保存每 300 毫秒重复一次的样本

如何确保某些代码每 1 秒运行不超过 1 次?

C++怎么能间隔一定的时间执行指定的代码

监控服务器日志,找出每分钟访问超过100次的ip地址

如何创建具有毫秒粒度的 Python 时间戳?