我有以下简短的代码示例,它显示了System.Threading.Timer的奇怪行为。在启动时,它显示两个计时器事件都发生在不同的时间戳。在一些击键(3到5)之后,两个计时器都是同步的。它们恰好同时发生。
这怎么可能?
可以使用System.Timers.Timer重现此行为。这是奇怪和意外的。我们正在寻找解释。
new Timer(_ => { Console.WriteLine("1: " + DateTime.UtcNow.ToString("HH:mm:ss,fffffff")); }, null, TimeSpan.FromMilliseconds(0), TimeSpan.FromMilliseconds(400));
Thread.Sleep(10);
new Timer(_ => { Console.WriteLine("2: " + DateTime.UtcNow.ToString("HH:mm:ss,fffffff")); }, null, TimeSpan.FromMilliseconds(0), TimeSpan.FromMilliseconds(400));
while (true)
{
Console.ReadKey();
// 100% CPU usage
for (var i = 0; i < Environment.ProcessorCount; ++i)
{
new Thread(() =>
{
for (var y = 0; y < 1 << 30; ++y) ;
}).Start();
}
}
请向右滚动以查看整行。
如果我更改代码以便它使用包含Thread.Sleep()
的线程,则所有代码都按预期工作。
这是工作示例:
static void Main(string[] args)
{
new Thread(Do).Start();
Thread.Sleep(10);
new Thread(Do).Start();
while (true)
{
Console.ReadKey();
// 100% CPU usage
for (var i = 0; i < Environment.ProcessorCount; ++i)
{
new Thread(() =>
{
for (var y = 0; y < 1 << 30; ++y) ;
}).Start();
}
}
}
private static void Do()
{
var id = Thread.CurrentThread.ManagedThreadId;
while (true)
{
Thread.Sleep(400);
Console.WriteLine(id + ": " + DateTime.UtcNow.ToString("HH:mm:ss,fffffff"));
}
}
编辑发现一些新闻
我们深入研究了System.Thrading.Timer
的系统源代码。 ctor接受回调并通过内部方法将其转发到TimerQueueTimer
- 对象。此对象在CallCallback
方法中调用此删除。该方法的唯一调用是Fire
。该方法的代码段是:
internal void Fire()
{
bool canceled = false;
lock (TimerQueue.Instance)
{
// prevent ThreadAbort while updating state
try { }
finally
{
canceled = m_canceled;
if (!canceled)
m_callbacksRunning++;
}
}
if (canceled)
return;
CallCallback();
我们在单例实例上发现lock
- 语句。目前我们认为这个锁定语句是定时器同步的原因。
让我们考虑一下:其中一个计时器目前在锁定声明中。现在我们浪费了大量的cpu时间,导致这个线程在锁内部停留一段时间。同时另一个线程完全达到了这个锁定语句。从现在开始他们是同步的。 (关于DateTime.UtcNow
的解决方案。
这可能吗?这可能是这种奇怪行为的解决方案吗?
答案 0 :(得分:0)
Windows不是实时操作系统。 DateTime.UtcNow
的分辨率低于每个线程运行所需的时间,因此它们似乎恰好在同一时间发生。
您需要使用更高分辨率的时间管理员才能看到它们不会在同一时间发生。请改用Stopwatch
。写下你的代码:
var sw = Stopwatch.StartNew();
new Timer(
_ => Console.WriteLine("1: " + sw.Elapsed.TotalMilliseconds),
null,
TimeSpan.FromMilliseconds(0),
TimeSpan.FromMilliseconds(400));
Thread.Sleep(10);
new Timer(
_ => Console.WriteLine("2: " + sw.Elapsed.TotalMilliseconds),
null,
TimeSpan.FromMilliseconds(0),
TimeSpan.FromMilliseconds(400));
然后你得到这个输出:
1: 0.2394 2: 11.1295 2: 452.4451 1: 452.5364 2: 986.9352 1: 986.9899 2: 1553.0689 1: 1553.104 2: 2102.9755 1: 2103.0126 2: 2623.3271 1: 2623.365 2: 3143.6929 1: 3143.7302 2: 3662.3903 1: 3662.4268 2: 4142.4248 1: 4142.465 2: 4629.2655 1: 4629.3208 2: 5160.6276 1: 5160.6878
显然他们没有同时执行。