высокочастотная синхронизация .NET

Я хочу создать высокочастотный поток обратного вызова. По сути, мне нужна функция для выполнения с регулярным высокочастотным (до 100 Гц) интервалом. Я понимаю, что Windows имеет нормальный срез выполнения потока ~ 15 мс. Я хотел бы указать регулярный интервал, который может быть быстрее, чем 15 мс.

Это то, что яЯ пытаюсь достичь. У меня есть внешнее устройство, которое необходимо отправить через определенный интервал. Интервал является переменным в зависимости от ситуации. Я ожидаю, что мне никогда не понадобится частота сообщений более 100 Гц (10 мс).

Я, конечно, могу реализовать спиновый цикл, однако я надеялся, что найдется решение, которое не потребовало бы так много потерянных ресурсов.

Предоставленные ссылки на вопросы / ответы не решают этот вопрос. Хотя я согласен с тем, что вопрос задавался несколькими различными способами, не было хорошего решения, которое действительно решило бы проблему.

Большинство предоставленных ответов говорят об использовании секундомера и выполнении задачи синхронизации вручную, что слишком сильно загружает процессор. Единственным жизнеспособным решением было использование мультимедийных таймеров, которые, как упоминал Хаанс, имели несколько подводных камней. Я нашел другое решение, однако ядобавлю ниже. Я не знаю о подводных камнях в это время, но я планирую сделать некоторое тестирование и исследование. Я все еще заинтересован в комментариях относительно решения.

WINAPI звонок через

BOOL WINAPI CreateTimerQueueTimer(
  _Out_     PHANDLE phNewTimer,
  _In_opt_  HANDLE TimerQueue,
  _In_      WAITORTIMERCALLBACK Callback,
  _In_opt_  PVOID Parameter,
  _In_      DWORD DueTime,
  _In_      DWORD Period,
  _In_      ULONG Flags
);

а также

BOOL WINAPI DeleteTimerQueueTimer(
  _In_opt_  HANDLE TimerQueue,
  _In_      HANDLE Timer,
  _In_opt_  HANDLE CompletionEvent
);

Ссылка на сайт -http://msdn.microsoft.com/en-us/library/windows/desktop/ms682485%28v=vs.85%29.aspx I 'Я использую PInvoke для достижения этой цели. Это также потребуется при работе с мультимедийными таймерами.

Моя подпись для тех, кто заинтересован.Pinvoke ссылка

[DllImport("kernel32.dll")]
static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer,
   IntPtr TimerQueue, WaitOrTimerDelegate Callback, IntPtr Parameter,
   uint DueTime, uint Period, uint Flags);

// This is the callback delegate to use.
public delegate void WaitOrTimerDelegate (IntPtr lpParameter, bool TimerOrWaitFired);

[DllImport("kernel32.dll")]
static extern bool DeleteTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer,
   IntPtr CompletionEvent);

используйте CreateTimerQueueTimer для запуска обратного вызова таймера. Используйте DeleteTimerQueueTimer, чтобы остановить обратный вызов таймера. Это несколько гибко, так как вы можете создавать собственные очереди. Однако самой простой реализацией, если требуется только один экземпляр, было бы использование очереди по умолчанию.

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

Секундомер со спиновым циклом - ~ 12-15% постоянной загрузки процессора (около 50% одного из моих ядер) CreateTimerQueueTimer - ~ 3-4% постоянной загрузки процессора

Я также чувствую, что обслуживание кода будет уменьшено с помощью опции CreateTimerQueueTimer. как это неНе требуется, чтобы логика была добавлена в ваш поток кода.

Ответы на вопрос(4)

Thread.Sleep() не кажется таким же неточным, как вы думаете, с такими небольшими интервалами. Следующее на Windows 8.1 x64 i7 Laptop .NET 4.0:

using System;
using System.Diagnostics;
using System.Threading;

namespace HighFrequency
{
    class Program
    {
        static void Main(string[] args)
        {
            var count = 0;
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            while(count <= 1000)
            {
                Thread.Sleep(1);
                count++;
            }
            stopwatch.Stop();
            Console.WriteLine("C# .NET 4.0 avg. {0}", stopwatch.Elapsed.TotalSeconds / count);
        }
    }
}

Выход:

C # .NET 4.0 в среднем +0,00197391928071928

Так что чуть меньше 2 мс! Я думаю, если бы было больше разногласий, которые могли бы вырасти довольно быстро, хотя у меня действительно есть несколько открытых приложений, просто ничего не решалось.

Во время сна в течение 10 мс, то есть 100 Гц, получил в среднем 0,0109 ... довольно много места!

Решение Вопроса

Eстьмного о неточной информации, распространяемой по ссылкам и комментариям. Да, по умолчанию прерывание по такту составляет 1/64 секунды = 15,625 мсек на большинстве машин, но его можно изменить. Также причина того, что некоторые машины работают с другой скоростью. API мультимедиа Windows, доступный от winmm.dll, позволяет вам с ним повозиться.

Что тыне хочу сделать это с помощью секундомера. Вы'Точное измерение интервала получится из него только тогда, когда вы используете его в горячей петле, которая постоянно проверяет, прошел ли интервал. Windows недобросовестно относится к такому потоку, когда его квант истекает, поток выигрываетЗапланировать запуск на некоторое время, когда другие потоки конкурируют за процессор. Этот эффект легко пропустить, так как вы неОбычно вы отлаживаете ваш код с другими процессами, которые активно работают и загружают процессорное время.

Функция, которую вы хотите использовать - это timeSetEvent (), она обеспечивает высокоточный таймер, который может достигать 1 миллисекунды. Он самокорректируется, сокращая интервал, если необходимо (и возможно), чтобы наверстать упущенное, когда предыдущий обратный вызов был задержан из-за ограничений планирования. Однако остерегайтесь того, что это трудно использовать, обратный вызов сделан из потока пула потоков, подобного System.Threading.Timer, поэтому обязательно используйте безопасную блокировку и примите контрмеры, которые гарантируют, что вы выиграли 'не получить неприятности от повторного входа.

Совершенно другой подход - timeBeginPeriod (), он изменяет частоту прерываний часов. У этого есть много побочных эффектов, для одного Thread.Sleep () становится более точным. Как правило, это более простое решение, поскольку вы можете сделать это синхронно. Просто поспи 1 мс, и тыЯ получу скорость прерывания. Некоторый код, с которым можно поиграть, демонстрирует, как это работает:

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

class Program {
    static void Main(string[] args) {
        timeBeginPeriod(10);
        while (!Console.KeyAvailable) {
            var sw = Stopwatch.StartNew();
            for (int ix = 0; ix < 100; ++ix) Thread.Sleep(1);
            sw.Stop();
            Console.WriteLine("{0} msec", sw.ElapsedMilliseconds);
        }
        timeEndPeriod(10);
    }

    [DllImport("winmm.dll")]
    public static extern uint timeBeginPeriod(int msec);
    [DllImport("winmm.dll")]
    public static extern uint timeEndPeriod(int msec);
}

Вывод на мою машину:

1001 msec
995 msec
999 msec
999 msec
999 msec
991 msec
999 msec
999 msec
999 msec
999 msec
999 msec
990 msec
999 msec
998 msec
...

Остерегайтесь, однако, проблемы с этим подходом, если другой процесс на вашей машине уже снизил частоту прерывания тактового генератора ниже 10 мсек, вы не получите ни секунды из этого кода. Тот'с ней очень неудобно иметь дело, вы можете сделать ее по-настоящему безопасной, только попросив 1 мсек и спать в течение 10 дней.Запустите это на машине с батарейным питанием. Сделайте пользу timeSetEvent ().

Обратите внимание, что одна из причин, почему 15 мс является эффективным минимумомSleep Время в том, что может потребоваться такой порядок, чтобы фактически поменять ваш процесс, поменять другой, дать ему некоторое время, чтобы что-то сделать, поменять его и снова поменять ваш. Предполагая, что у вас есть несколько ядер, лучше использовать одно ядро для обновления 100 Гц, особенно если вы знаете, когдаЯ хотел бы пропустить несколько циклов для сгенерированной вручную сборки мусора и / или использовать C / C ++, как предложено.

Вы можете попробоватьСекундомер, который использует те же высокопроизводительные таймеры, что и DirectX.

Ваш ответ на вопрос