Странное поведение C# .NET ManualResetEvent после запуска ПК

Недавно я заметил очень странное поведение класса ManualResetEvent в .NET framework. Я использую С#, VS 2015, цель проекта установлена ​​​​на 4.5.2. Вот полный код:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace CSharpCOnsole
{
    class Program
    {
        private static ManualResetEvent exit = new ManualResetEvent(false);

        static void Main(string[] args)
        {
            var t = Task.Factory.StartNew(F);
            Console.ReadKey();
            exit.Set();
            t.Wait();
            exit.Close();
        }

        static void F()
        {
            var dtStopwatch = new Stopwatch();
            uint ii = 0;
            while (!exit.WaitOne(25)) {
                dtStopwatch.Stop();
                var dt = 1000.0 * dtStopwatch.ElapsedTicks / Stopwatch.Frequency;
                dtStopwatch.Restart();

                if (ii++ % 40 == 0) {
                    Console.WriteLine(dt.ToString("F3"));
                }
            }
        }
    }
}

Я знаю, что это может звучать глупо, но вот что происходит: если я перезагружаю свой компьютер, запускаю VS сразу после загрузки и запускаю эту программу, я получаю следующий вывод:

31.665
31.365
31.541
...

Более того, если я изменю 25 в !exit.WaitOne(25) на любое другое число в диапазоне от 16 до 31, я получу тот же результат: он ждет 31 мс. Если я выберу любое число в диапазоне от 1 до 15, оно будет ждать ровно 16 мс. И так далее, если я выберу любое число в диапазоне от 32 до 47, оно будет ждать ровно 48 мс. НО: если я скомпилирую и запущу этот код несколько раз (около 10-30) или подожду какое-то время (около 5-20 минут после загрузки), он вдруг начнет нормально работать! Да, звучит нелепо, но так бывает. Он начинает блокировать цикл на заданное время с точностью до 1 мс. И так до следующей перезагрузки ПК. Я пробовал это на двух разных ПК и получил такое же поведение. Гугление абсолютно ничего не дало по этой проблеме. Если я запускаю скомпилированный EXE без VS, я получаю такое же поведение.


person iamnp    schedule 05.11.2015    source источник
comment
Похоже, это может быть просто какая-то фоновая служба, которая запускается и в какой-то момент изменяет точность часов.   -  person Jon Skeet    schedule 05.11.2015
comment
вдруг начинает нормально работать Возможно, какая-то другая программа подняла разрешение таймера. Chrome сделал это некоторое время. Разряжает батарею.   -  person usr    schedule 05.11.2015
comment
Возможно, временной интервал времени процессора установлен на интервал 16 мс, чтобы переключаться между выполнением программы, когда она находится под слишком большой нагрузкой запуска. Как только у него будет больше ресурсов, он вернется, чтобы иметь возможность переключиться на программу/поток, которому требуется время процесса. Вы можете проверить обозреватель процессов (sysinternals), чтобы увидеть нагрузку на систему, или диспетчер задач, чтобы получить информацию о нагрузке. (Я бы предположил, что компиляция - это не точка, а время, необходимое для компиляции). После того, как нагрузка упала, проверьте, получаете ли вы правильный результат.   -  person Uwe Hafner    schedule 05.11.2015


Ответы (2)


Ожидание любого вида WaitHandle гарантированно будет как минимум столько времени, сколько указано в тайм-ауте, но возможно/вероятно дольше. Фактическое время ожидания определяется системными часами и степенью занятости системы. Если вы хотите сделать точную синхронизацию, WaitHandles или классы, использующие их (например, ManualResetEvent), не подходят.

person lsedlacek    schedule 05.11.2015
comment
Так что же предлагаете, чтобы заблокировать игровой цикл? Thread.Sleep имеет такое же странное поведение в течение 5-20 минут после запуска ПК. - person iamnp; 05.11.2015
comment
Можно просто выполнить Thread.Sleep(0) или Thread.Yield() в цикле, как предлагается здесь: stackoverflow.com/a/25497775/5401421 - person lsedlacek; 05.11.2015

Я нашел ответ - вы можете установить разрешение системного таймера, вызвав TimeBeginPeriod https://stackoverflow.com/a/15071477/1389883

Или вы можете использовать Multimedia Timer API: https://stackoverflow.com/a/24843946/1389883

person iamnp    schedule 05.11.2015
comment
Это устанавливает глобальное разрешение таймера. Пожалуйста, не делайте этого, если вы не знаете, что делаете, и не работаете на выделенном компьютере. Вместо этого рассмотрите это если вам действительно нужна такая точность. - person lsedlacek; 06.11.2015