我有一个创建新计时器的类,并在每个tick上轮询一个远程队列(通过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);
}
}
如果我创建了这个类的八个新实例,将轮询间隔设置为250ms,并调用它们,我会假设计时器会非常准确地打勾。在计时器回调中执行的内容应该无关紧要。但事实并非如此。
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,......?
答案 0 :(得分:3)
定时器不是无限准确的,并且线程调度不是无限准确的。它们中的任何一个都具有最好的精度(可通过timeBeginPeriod
配置),在当前版本的Windows上默认为16.7ms(在15岁以上的Windows上约为50ms)。
计时器本身并不是无限精确的,它会生成一个事件,使线程池中的线程准备好,这将在以后的任何时间安排,不准确,并且没有任何硬保证。如果其他线程正在运行,那么“任何时候以后”可能会根据处理器负载和线程优先级,随后(或者甚至从不)随时 。
仅此一点已经足以说明为什么通常没有你选择的间隔是准确的(除了重合),但特别是250ms的间隔不会(因为249ms是调度程序的默认粒度的最接近的倍数,所以一个新的线程最多 可以在265.6ms之后进行调度 - 这假设核心在该确切时间可用,这是不可保证的。)
此外,处理HTTP请求可能需要非常重要的时间,因此即使没有其他线程,您也可能创建比核心更多的工作者。这必然意味着操作系统必须决定在特定时间安排哪一个。无论调度程序做什么,最终总会对一个或多个线程产生“不公平”的情况,导致它们“不准确”。
写入控制台也是如此。对控制台的写入是同步的(即,当两个线程写入控制台时,单个写入可能以任何顺序出现,但它们不会出现乱码)。这必然意味着有锁。谁先得到锁,继续。谁试图在一个纳秒之后获得锁定将被阻止。其他一些线程接管时间片。最终,被阻止的线程将再次“准备好”,但没有严格保证此线程将立即运行。
准备好跑步和跑步是两回事。