Таймер событий задерживается в .NET, а не в Mono. Это ошибка?

Альтернатива DotNet (MONO- http://www.go-mono.com/mono-downloads/download.html) правильно обрабатывает условие короткого таймера (время срабатывания до ~ 12 мс). Тем не менее, DotNet, похоже, занимает больше времени, чем ожидалось, и "промахивается". Итак, какое поведение правильное? Правильно ли моно отслеживает срабатывание своего события? Может, он подставляет свой номер из-за неправильного секундомера? или в DotNet есть «ленивый» таймер событий? С моим тестированием ясно, что это не основная проблема Windows.

.NET 200ms+ Interval minimum

Mono 12ms+ Interval minimum

Этот запрос аналогичен следующему: Таймер занимает на 10 мс больше, чем интервал .

Однако рассмотрим код:

В .NET пропущенные события будут медленно расти:

Total Runtime/Interval - Actual Events = Missed Events

Код:

class Program
{
    const int Interval = 60; //ms Event fireing

    Stopwatch TotalRunTime = new Stopwatch();//Full runtime.
    Stopwatch Calctime = new Stopwatch(); //Used to load up the Event to 66 percent

    System.Timers.Timer TIMER; //Fireing Event handler.
    int TotalCycles = 0; //Number of times the event is fired.
    int Calcs_per_cycle = 100; // Number of calcs per Event.

    static void Main(string[] args)
    {
        Program P = new Program();
        P.MainLoop();
    }

    void MainLoop()
    {
        Thread.CurrentThread.Priority = ThreadPriority.Highest;

        TIMER = new Timers.Timer();
        TIMER.Interval = Interval;
        TIMER.Elapsed += new ElapsedEventHandler(MS_Calc);
        TIMER.AutoReset = true;
        TIMER.Enabled = true; //Start Event Timer
        TotalRunTime.Start(); //Start Total Time Stopwatch;

        while (true)
        {
            Thread.Sleep(Interval * 5);
            Console.Clear();
            PrintAtPos(2, "Missed Events " + (((TotalRunTime.ElapsedMilliseconds / Interval) - TotalCycles).ToString()));

        }

    }
    public void MS_Calc(object source, System.Timers.ElapsedEventArgs E)// public void MS_Calc(object source, ElapsedEventArgs E)
    {
        TotalCycles++;
        Calctime.Start();
        for (int i = 0; i < Calcs_per_cycle; i++)
        {
            int A = 2;
            int B = 2;
            int c = A + B;
        }
        PrintAtPos(1, "Garbge Collections G1: " + GC.CollectionCount(0) + "  G2: " + GC.CollectionCount(1) + "  G3: " + GC.CollectionCount(2) + " ");
        Calctime.Stop();
        if (Interval * 0.667 > Calctime.ElapsedMilliseconds) //only fill the even timer 2/3s 
        //full to make sure we finish before the next event
        {
            Calcs_per_cycle = (int)(Calcs_per_cycle * 1.035);
        }
        Calctime.Reset();
        PrintAtPos(0, "Calc Time : " + (DateTime.Now - E.SignalTime).Milliseconds + "  Calcs/Cycle: " + Calcs_per_cycle);
    }

    private static void PrintAtPos(int Vertical_Pos, string Entry)
    {
        Console.SetCursorPosition(0, Vertical_Pos);
        Console.Write(Entry);
    }
}

person PizzaRoll    schedule 20.12.2012    source источник
comment
Боковое примечание: у вас многопоточный код без какой-либо синхронизации ... В его нынешнем виде это ничего не доказывает ...   -  person Alexei Levenkov    schedule 20.12.2012
comment
@AlexeiLevenkov единственное поле, к которому обращаются несколько потоков, - это TotalCycles (на самом деле оно записывается только одним потоком), и результаты будут такими же, если вы сделаете это volatile и используете Interlocked.Increment.   -  person Mike Zboray    schedule 20.12.2012
comment
@AlexeiLevenkov Я думаю, вы можете утверждать, что обработчик запускается в потоке пула потоков, поэтому вы не знаете, в каком потоке он находится. Однако calcs_per_cycle ограничен, чтобы он не увеличивался больше, чем 2/3 интервала (честно говоря, я не вижу смысла в этом цикле). Все это в значительной степени не имеет значения, потому что суть проблемы заключается в пропущенных событиях, а не в 0 GC или продолжительности цикла вращения.   -  person Mike Zboray    schedule 20.12.2012
comment
@mikez, да. Весь обработчик мог быть исключен из выборки, оставив только totalCycles++ для того же эффекта (или Thread.Sleep((int)(Interval * 0.667)); для имитации тяжелых вычислений).   -  person Alexei Levenkov    schedule 20.12.2012


Ответы (1)


Проблема в том, что системные часы Windows неточны по шкале времени, которую вы использовали, и я считаю, что реализация Microsoft Timer использует системные часы, чтобы определить, когда запускать событие Elapsed. Я предполагаю это, потому что вы получаете довольно точное описание наблюдаемого поведения, когда учитываете (не) точность системных часов.

Системные часы имеют точность только до 15,6 мс (причина в том, чтобы продлить срок службы батареи, см. этот вопрос). Таким образом, событие Elapsed срабатывает не каждые 60 мс, а только каждые 62,4 мс. Суммируется разница в 2,4 мс, и вы «пропустили» события. Я запустил вашу программу в течение 33009 мс, и мне было сообщено о 22 пропущенных событиях. Я ожидал бы разницы в 33009 * (1/60 - 1/62.4) = 21.2 событиях. Это примерно столько, сколько вы собираетесь получить, учитывая, что у нас нет этого числа 15,6 мс с более высокой точностью. (Например, если затраченное время 62,5 мс, вы получите 22,0 ожидаемых промаха).

Ваш пример явно демонстрирует, что реализация Timer может быть более точной. Я не знаю, почему реализация Mono более точна, но обычно таймеры не предназначены для точного времени в миллисекундах. Существуют и другие методы синхронизации, которые более точны, чем системные часы, которые используются классом Stopwatch, когда они доступны.

person Mike Zboray    schedule 20.12.2012
comment
@mike z Я согласен, это хорошее описание механизма в действии. Интервал в 78 мс решает недостающую проблему. Однако не должно ли это исправить проблему 15,6 мс? Это не похоже на Таймер. `[DllImport (winmm.dll, EntryPoint = timeBeginPeriod, SetLastError = true)] частный статический внешний uint TimeBeginPeriod (uint uMilliseconds); - person PizzaRoll; 20.12.2012