【问题标题】:What makes my timers run unaccurate? [closed]是什么让我的计时器运行不准确? [关闭]
【发布时间】:2013-03-10 12:18:00
【问题描述】:

我有一个创建新计时器的类,并在每次滴答时轮询远程队列(通过 HTTP)。

public void Start()
{
    _timer = new Timer((x) =>
    {
        Console.WriteLine(DateTime.Now.ToString("hh:MM:ss.fff") + " " + typeof(T).Name);                

        var message = (Message)null;
        var messageBody = (T)null;

        try
        {
            if (!_queue.TryGet(out message))
                return;

                messageBody = (T)JsonConvert.DeserializeObject(message.Body, typeof(T));

                _messageDispatcher.Dispatch<T>(messageBody);

                _queue.Delete(message.Id);
            }
            catch (Exception ex)
            {
                _errorHandler.Handle(ex, message);
            }                
        }, null, 0, _queueConsumerConfiguration.PollingInterval);            
    }
}

如果我创建这个类的八个新实例,将轮询间隔设置为 250 毫秒,然后调用它们,我会假设计时器会非常准确地计时。计时器回调中执行的内容无关紧要。然而事实并非如此。

01:03:23.305 MessageSleepForOneSecond
01:03:23.301 MessageSleepForOneSecond
01:03:23.297 MessageSleepForOneSecond
01:03:23.316 MessageSleepForOneSecond
01:03:24.321 MessageSleepForOneSecond
01:03:24.562 MessageSleepForOneSecond
01:03:24.701 MessageSleepForOneSecond
01:03:24.707 MessageSleepForOneSecond
01:03:24.716 MessageSleepForOneSecond
01:03:25.321 MessageSleepForOneSecond
01:03:25.518 MessageSleepForOneSecond
01:03:25.764 MessageSleepForOneSecond
01:03:25.912 MessageSleepForOneSecond
01:03:25.920 MessageSleepForOneSecond
01:03:25.924 MessageSleepForOneSecond
01:03:26.521 MessageSleepForOneSecond
01:03:26.710 MessageSleepForOneSecond
01:03:26.957 MessageSleepForOneSecond
01:03:27.107 MessageSleepForOneSecond
01:03:27.120 MessageSleepForOneSecond
01:03:27.126 MessageSleepForOneSecond
01:03:27.716 MessageSleepForOneSecond
01:03:27.906 MessageSleepForOneSecond
01:03:28.151 MessageSleepForOneSecond
01:03:28.305 MessageSleepForOneSecond
01:03:28.316 MessageSleepForOneSecond
01:03:28.322 MessageSleepForOneSecond
01:03:28.913 MessageSleepForOneSecond
01:03:29.100 MessageSleepForOneSecond
01:03:29.349 MessageSleepForOneSecond
01:03:29.502 MessageSleepForOneSecond
01:03:29.513 MessageSleepForOneSecond
01:03:29.538 MessageSleepForOneSecond
01:03:30.107 MessageSleepForOneSecond
01:03:30.297 MessageSleepForOneSecond
01:03:30.545 MessageSleepForOneSecond
01:03:30.705 MessageSleepForOneSecond
01:03:30.712 MessageSleepForOneSecond
01:03:30.733 MessageSleepForOneSecond
01:03:31.307 MessageSleepForOneSecond
01:03:31.310 MessageSleepForOneSecond
...

发生了什么事?是什么导致不准确? ThreadPool、Windows、...的管理?

【问题讨论】:

  • “我认为计时器会非常准确地计时” 为什么? Windows 不是实时操作系统。您的代码与大量其他代码共享处理器时间。
  • 为了获得更高的准确性,让计时器每毫秒运行一次,并仅在达到间隔时计算自己执行的经过时间。
  • @CodyGray 没错,我的假设是错误的。但是什么是解释它的好方法? Windows 花费了太多时间来切换上下文,以至于不可能准确?
  • 为什么“定时器回调中执行的内容”无关紧要?每个班级只有一个计时器。如果 HTTP 通信花费的时间长于计时器间隔,则计时器无法按时触发。
  • 我不知道为什么这个问题被关闭为“没有建设性”,所以我投票重新打开它。 这绝对是一个可以用事实和参考来专业回答的问题。事实上,在这方面它已经得到了很好的回答。我不明白为什么人们认为“这个问题可能会引发辩论、争论、投票或扩展讨论”。尤其是鉴于杰夫的最后评论:“我想知道什么是解释为什么会发生的好方法。”也许这应该在问题本身中更加清楚......

标签: c# .net windows multithreading timer


【解决方案1】:

定时器不是无限准确的,线程调度也不是无限准确的。它们中的任何一个都具有最多 1 毫秒的精度(可通过 timeBeginPeriod 配置)和当前版本的 Windows 上默认为 16.7 毫秒(在 15 年以上的旧 Windows 上约为 50 毫秒)。

一个计时器本身并不是无限准确的,它会生成一个事件,使线程池中的线程准备好,该事件将在以后的任何时间安排,不准确,也没有任何硬性保证。如果其他线程正在运行,那么根据处理器负载和线程优先级,“以后的任何时间”可能会任何时间之后(甚至永远不会)。

仅此一点就足以说明为什么您选择的时间间隔通常都不准确(巧合除外),但特别是 250 毫秒的时间间隔不会准确(因为 249 毫秒是调度程序默认粒度的最接近的倍数,所以一个新的线程充其量可以在 265.6 毫秒之后调度——这假设在那个确切的时间有一个内核可用,这是不能保证的)。

此外,处理 HTTP 请求可能需要相当长的时间,因此即使没有其他线程,您创建的工作线程也很有可能比内核数量多。这必然意味着操作系统必须决定在特定时间安排哪一个。无论调度器做什么,最终总会有一个或多个线程出现“不公平”的情况,导致它们“不准确”。

写入控制台也是如此。对控制台的写入是同步的(即,当您的两个线程写入控制台时,单个写入可能以任何顺序出现,但不会出现乱码)。这必然意味着有一个锁。谁先拿到锁,谁就继续。任何试图在纳秒后获得锁的人都将被阻止。其他一些线程接管时间片。最终,被阻塞的线程将再次“准备好”,但不能严格保证该线程会立即运行。

准备好跑步和跑步是两件截然不同的事情。

【讨论】:

  • 完全有道理,这正是我正在寻找的答案类型。
  • 仅供参考,在这种特殊情况下,线程池的线程用完了,因为执行回调需要超过 250 毫秒。
  • 更准确地说,启动 new 线程以供线程池使用需要时间。
  • @JefClaes:是的,这是导致问题的另一个原因,是的。创建线程是一种重量级操作,其中包括为每个加载的库调用DllMain。此外,这是序列化的(即每个进程一次只启动一个线程),这很可能导致线程错过其“约会”。
  • 最后要考虑的一件事是,一旦线程池被占用(更多线程>处理器),它将等待 500 毫秒,然后再启动一个新线程。
猜你喜欢
  • 2015-10-28
  • 2011-04-15
  • 2017-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-25
  • 1970-01-01
  • 2020-11-22
相关资源
最近更新 更多