2015-05-12 2 views
0

У меня есть метод, который я хочу запустить несколько тысяч раз в секунду. В настоящее время я запускаю его со скоростью примерно один раз в миллисекунду с помощью DispatcherTimer, при этом свойство Interval установлено равным 1ms.Вызов метода на максимально возможном интервале (таймеры)

Теперь DispatcherTimer не является самым быстрым, самым высокоточным таймером, и поскольку обработчик события Tick работает в потоке пользовательского интерфейса, он не будет выполнять гораздо чаще, чем это. Код, который я вызываю, не нужно запускать в потоке пользовательского интерфейса, поэтому мне кажется, что я ограничиваю себя без всякой причины.

Каковы мои варианты, если я хочу более высокую частоту? Должен ли я использовать System.Threading.Timer? Существует ли конкретный таймер для высокочастотных высокоточных операций? Должен ли я просто отказаться от таймеров вообще и посмотреть на что-то еще?

Обратите внимание, что хотя я попытался (но не смог из-за отсутствия репутации) пометить это как WindowsIot (потому что мне нужно его запустить там, и, возможно, в библиотеке новых устройств есть вещи специально для этого), это относится к .net приложениями в целом также являются: WPF, WinForms, WinRT и т. д.

+2

Какова ваша цель? Почему вам нужны тысячи таймеров? –

+2

@ Генерал-Дюмон ОП говорил тысячу раз. не тысячи таймеров –

+6

Что вы пытаетесь достичь? Что этот метод должен делать? –

ответ

2

Вы можете использовать бесконечный цикл и сами провести время, используя Stopwatch. Это использует собственный метод rdtsc, который учитывает фактические циклы ЦП. Это намного точнее, чем другие таймеры в .NET. Вы можете избежать накладных расходов Секундомера и вызвать собственные методы directly. Если вы вызываете собственные методы, вы, вероятно, захотите использовать QueryPerformanceCounter.

4

Основная проблема заключается в том, что когда вы получаете процессорное время, вы делаете это в системе планирования ОС. Это означает, что операции, такие как Thread.Sleep, или, действительно, ожидание обратного вызова системного таймера, ограничены частотой системного таймера. Это обычно 15,6 мс - намного дольше, чем ваши 1 мс. Способ WPF обойти это, чтобы обеспечить плавную анимацию и т. Д., Заключается в том, что во время анимации он изменит частоту глобального таймера. Минимальный размер составляет 0,5 мс, который может быть достаточно хорош для вас - при условии, что остальная часть кода может выполняться в 0,5 мс (в общей сложности 1 мс «время кадра»). Очевидно, что довольно сложно иметь надежную частоту.

Так что это сводится к тому, почему вы пытаетесь запустить что-то ровно в 1000 раз в секунду, и какой вы делаете в цикле.

И вы почти гарантированы не для получения такого разрешения. Даже если вы установите глобальный таймер на 0,5 мс и сделаете Timer, который выполняется каждую секунду, вы все равно будете сражаться с другими потоками, которые пытаются выполнить.

Одним из подходов, используемых в играх, является использование таймера с высоким разрешением - QueryPerformanceCounter, используемого внутри Stopwatch. Однако это не позволяет вам получить доход - вам нужно активно вращаться (если вы не можете Thread.Sleep достаточно долго, чтобы позволить вам использовать это - см. Выше). И обратите внимание, что большинство игр, в которых используется фиксированный шаг синхронизации - что-то вроде вашего сценария, обычно ограничено примерно 60 FPS - почти 17 мс за цикл. Это далеко от 1 мс за цикл. Если вы не используете фиксированную шаговую синхронизацию, вы можете сделать много классных вещей, но вам нужно учитывать фактическую длину временного шага - не совсем просто и, возможно, не применимо к вашему делу.

+0

Интересный материал. Думаю, мне нужно запустить какой-то счетчик в моем текущем коде - с помощью DispatcherTimer - посмотреть, как часто он работает -активно. Может быть, он не будет сильно отличаться (воспринимаемой) разницей, я использую DispatcherTimer или какой-то другой таймер. Я отчитаю. Спасибо за проницательный пост! – Bas

0

Как упоминает Luaan, вы находитесь во власти ОС, если используете Sleep.

Если вам нужен точный интервал, то SpinWait и SpinUntil - это то, что вы хотите. Обратите внимание, что это продолжит выполнение кода до тех пор, пока условие/таймеры не будет удовлетворено, и поэтому ваш поток будет занимать 100% времени процессора на ядре, в котором вы выполняете. (Если вы используете 4-ядерную машину, вы увидите 25% использования ЦП).

Это не гарантирует, что вы выполните точно так, как хотите - ОС по-прежнему свободна, чтобы украсть процессорное время и запланировать выполнение других потоков. Единственный способ гарантировать выполнение с точным интервалом - использовать Realtime OS как VxWorks. У этого есть гарантии для обеспечения заданного распределения времени процессора. Конечно, RTOS не будет запускать .NET - так что вам придется использовать другой язык.

1

Я написал код. Помните, что это не лучшее решение, но оно работает.

Класс FrequencyRunner:

public class FrequencyRunner 
{ 
    private int _frequency; 
    private Action _action; 
    private bool _isStarted = false; 

    /// <summary> 
    /// частота 
    /// </summary> 
    public int Frequency { get { return _frequency; } set { _frequency = value; } } 

    /// <summary> 
    /// задача 
    /// </summary> 
    public Action Action { get { return _action; } set { _action = value; } } 

    /// <summary> 
    /// конструктор 
    /// </summary> 
    public FrequencyRunner() 
    { 
     _frequency = 1000; 
     _action =() => { }; 
    } 

    /// <summary> 
    /// конструктор 
    /// </summary> 
    /// <param name="frequency"> частота </param> 
    /// <param name="action"> задача </param> 
    public FrequencyRunner(int frequency, Action action) 
    { 
     _frequency = frequency; 
     _action = action; 
    } 

    /// <summary> 
    /// запустить процесс 
    /// </summary> 
    public void Start() 
    { 
     _isStarted = true; 
     Task.Run(() => 
     { 
      Stopwatch sw = new Stopwatch(); 
      double interval = 1000/_frequency; 

      while (_isStarted) 
      { 
       sw.Restart(); 
       _action(); 
       sw.Stop(); 

       double timeLeft = sw.ElapsedMilliseconds; 
       if (timeLeft < interval) 
       { 
        // need wait some time... 
        sw.Restart(); 
        timeLeft = interval - timeLeft; 
        while (sw.ElapsedMilliseconds < timeLeft) 
        { 
         // nothing to do 
        } 
        sw.Stop(); 
       } 
      } 
     }); 
    } 

    /// <summary> 
    /// завершить процесс 
    /// </summary> 
    public void Stop() 
    { 
     _isStarted = false; 
    } 
} 

Использование:

// run it for 2 seconds (25 times per second) 
int i = 0; 
var runner = new FrequencyRunner(25,() => Console.WriteLine("Counter: {0}", ++i)); 
runner.Start(); 
Thread.Sleep(2000); 
runner.Stop(); 
Смежные вопросы