C# неточности измерения времени цикла

Я пытаюсь создать метод в C#, с помощью которого я могу многократно выполнять действие (в моем конкретном приложении это отправляет пакет UDP) с заданной скоростью. Я знаю, что неточность таймера в C# будет препятствовать тому, чтобы выходные данные были точно на целевой скорости, но наилучшее усилие достаточно хорошо. Тем не менее, при более высоких ставках сроки, кажется, полностью выключены.

while (!token.IsCancellationRequested)
{
    stopwatch.Restart();

    Thread.Sleep(5); // Simulate process

    int processTime = (int)stopwatch.ElapsedMilliseconds;
    int waitTime = taskInterval - processTime;

    Task.Delay(Math.Max(0, waitTime)).Wait();
}

Смотрите здесь полный пример консольного приложения.

При запуске выход FPS этого тестового приложения показывает около 44-46 Гц для цели 60 Гц. Однако при более низких скоростях (скажем, 20 Гц) выходная скорость намного ближе к цели. Я не могу понять, почему это так. В чем проблема с моим кодом?

2 ответа

Проблема в том, что Thread.Sleep (или же Task.Delay) не очень точный. Взгляните на это: Точность Task.Delay

Один из способов исправить это - запустить таймер один раз, а затем создать цикл, в котором вы задерживаете примерно ~15 мс на каждую итерацию. Внутри каждой итерации вы вычисляете, сколько раз должна была быть выполнена операция, и сравниваете ее с тем, сколько раз вы ее выполнили. И тогда вы запускаете операцию достаточно раз, чтобы наверстать упущенное.

Вот пример кода:

private static void timerTask(CancellationToken token)
{
    const int taskRateHz = 60;

    var stopwatch = new Stopwatch();

    stopwatch.Start();

    int ran_so_far = 0;

    while (!token.IsCancellationRequested)
    {
        Thread.Sleep(15);

        int should_have_run =
            stopwatch.ElapsedMilliseconds * taskRateHz / 1000;

        int need_to_run_now = should_have_run - ran_so_far;

        if(need_to_run_now > 0)
        {
             for(int i = 0; i < need_to_run_now; i++)
             {
                 ExecuteTheOperationHere();
             }

             ran_so_far += need_to_run_now;
        }
    }
}

Обратите внимание, что вы хотите использовать longс вместо intЕсли процесс должен оставаться живым в течение очень долгого времени.

Если вы замените это:

Task.Delay(Math.Max(0, waitTime)).Wait();

с этим:

Thread.Sleep(Math.Max(0, waitTime));

Вы должны получить более близкие значения на более высоких ставках (зачем вам использовать Task.Delay(..).Wait() тем не мение?).

Другие вопросы по тегам