According to the documentation最大dueTime值为4294967294毫秒。但是,如果将计时器设置为此值,则会立即触发回调。
运行以下程序并单击“c” - 回调将立即触发,单击“o”,它将在49天后触发,单击“v”将在1秒后触发。这是一个错误还是我做错了什么?
using System;
using System.Threading;
namespace Test
{
class Program
{
private static bool isRunning;
private static Timer t;
static void Main(string[] args)
{
t = new Timer(OnTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
isRunning = true;
while (isRunning)
{
var command = Console.ReadKey();
switch (command.KeyChar)
{
case 'q':
isRunning = false;
break;
case 'c':
t.Change(TimeSpan.FromMilliseconds(4294967294), TimeSpan.FromMilliseconds(-1));
break;
case 'o':
t.Change(TimeSpan.FromMilliseconds(4294000000), TimeSpan.FromMilliseconds(-1));
break;
case 'v':
t.Change(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(-1));
break;
}
}
Environment.Exit(0);
}
private static void OnTimerCallback(object x)
{
Console.WriteLine("Timer callback");
t.Change(TimeSpan.FromMilliseconds(4294967294), TimeSpan.FromMilliseconds(-1));
}
}
}
更新
如果在回调中将timer设置为max dueTime值,它将不会立即触发并按预期工作:)
答案 0 :(得分:2)
这看起来像是CLR实现计时器的竞赛。我需要在确切的原因上挥手,我真的不明白它怎么会出问题。至少从查看SSCLI20源代码(clr \ src \ vm \ win32threadpool.cpp)开始,使用当前发货代码的情况很可能不再准确。
我可以通过在Change()调用中设置断点来轻松地重现问题,但不能让代码在没有中断的情况下运行。它的行为类似于从GetTickCount()返回的最后一个观察值而不是当前值计算到期时间。将0xfffffffe添加到该陈旧值,然后计算已过期的时间。滴答计数仅具有32位分辨率。我认为真正的竞争在于计算SleepEx()等待下一次到期事件的时间,但这是猜测。
如果不使用调试器暂停计时器线程,也可能发生此竞争。虽然它可能很少见。我不得不建议你远离接近0xfffffffe的任何值,以确保不会发生这种情况。没有困难,24天睡觉的效率并不比睡眠时间长49天:)通过自己计算睡眠时间来实现更长的间隔时间。
答案 1 :(得分:1)
这实际上是一个有趣的问题。我试着在反射器中查看该方法,但结果是本地内部调用。
但你有什么理由想这么做吗?根据文档,您可以将dueTime
更改为-1
或Timeout.Infinite
以禁用它,就像您在构造函数中所做的那样。
您也可以使用:
t.Change(4294967294u, -1u);
而不是
t.Change(TimeSpan.FromMilliseconds(4294967294), TimeSpan.FromMilliseconds(-1));
答案 2 :(得分:1)
根据文档,最大dueTime值为4294967294毫秒。
4294967295为UIn32.MaxValue
,因此4294967294按位转换为int
为-2。
但我在文档中看不到任何明确表示此值正常的内容。列出0和-1。
[请参阅评论:这只是转换为UInt32
]您是否考虑使用Timer.Change
的long
重载,这会产生更大的范围?
附加:查看SetWaitableTimer
的文档(我知道System.Threading.Timer
是基于Win32 Waitable Timers构建的)我看到了适当的时间参数:
负值表示相对时间。
因此-2可能意味着从现在起200ns开火,这基本上是立即的。