2016-04-24 3 views
-1

Я пытаюсь создать метод на C#, с помощью которого я могу многократно выполнять действие (в моем конкретном приложении он отправляет UDP-пакет) с заданной скоростью. Я знаю, что неточность таймера в C# будет препятствовать тому, чтобы результат был точно на целевой скорости, но лучшее усилие достаточно хорошее. Однако при более высоких скоростях время, похоже, полностью отключается.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(); 
} 

См here для полного приложения Пример консоли.

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

+0

Вы получаете отрицательные значения 'waitTime'? Если это так, то вы не можете обрабатывать достаточное количество запросов в секунду. –

+1

Кстати, пожалуйста, напишите пример кода в вопросе. Попробуйте прийти с минимальным примером. –

+0

Нет, при работе с целевой скоростью 60 Гц значения myTimeTime равны 10-11 мс. И это самый минимальный пример, который может продемонстрировать проблему. – impsnldavid

ответ

1

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

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

с этим:

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

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

+0

Спасибо, что исправляет производительность. Моя библиотека ориентирована на .NET Platform Standard, однако Thread.Sleep недоступен для меня. – impsnldavid

+0

@HenkHolterman Мне также интересно, почему до сих пор не нашел окончательного ответа. Но он «исправляет» проблему, о которой идет речь (не так, потому что, как вы упомянули в комментарии, это не так, как это должно быть сделано в любом случае). – Evk

2

Проблема в том, что Thread.Sleep (или Task.Delay) не очень точен. Посмотрите на это: Accuracy of 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 сек, если процесс должен остаться в живых в течение очень долгого времени.

+1

@HenkHolterman, что должно быть хорошо, поскольку код обнаружит, что ему нужно выполнить операцию более одного раза и запустить их немедленно, чтобы догнать. –

+1

Это означает, что время выполнения неравномерно распределено по-разному, но гарантируется, что в среднем 60 операций выполняются за секунду с разумной точностью. –

+0

@HenkHolterman, ответ обновлен –