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()
тем не мение?).