高频时序.NET

我正在寻找创建一个高频回调线程。 基本上我需要一个以常规高频(高达100Hz)间隔执行的function。 我意识到Windows有一个正常的线程执行切片是〜15ms。 我想指定一个可以快于15ms的常规间隔。

这就是我想要完成的。 我有一个需要以一定间隔发送消息的外部设备。 间隔根据情况而变化。 我希望我不需要超过100Hz(10ms)的消息速率。

我当然可以实现一个自旋循环,但是,我希望有一个解决方案不需要浪费太多资源。

提供的问题/答案链接无法解决此问题。 虽然我同意这个问题有几种不同的问题,但实际上并没有一个好的解决方案可以解决问题。

提供的大多数答案都与使用秒表和手动执行计时任务有关,这完全是CPU密集型的。 唯一可行的解​​决方案是使用多媒体定时器,这有一些陷阱,正如Haans所说。 我找到了另一种解决方案,但我将在下面添加。 我不知道此时的陷阱,但我打算做一些测试和研究。 我仍然对有关解决方案的评论感兴趣。


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我正在使用PInvoke来实现这一目标。 然而,在处理多媒体定时器时也需要这样做。

我的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停止计时器回调。 这有点灵活,因为您也可以创建自定义队列。 但是,如果只需要一个实例,最简单的实现方法是使用默认队列。

我使用带有旋转循环的秒表在第一侧测试了这个解决方案,我收到的关于时序的结果几乎相同。 但是,我的机器上的CPU负载明显不同。

具有旋转循环的秒表 – 约12-15%的恒定CPU负载(我的一个核心的约50%)CreateTimerQueueTimer – ~3-4%的恒定CPU负载

我还觉得使用CreateTimerQueueTimer选项可以减少代码维护。 因为它不需要将逻辑添加到您的代码流中。

通过链接和评论传播了许多不准确的信息。 是的,默认情况下,大多数机器上的时钟节拍中断是1/64秒= 15.625毫秒,但可以更改。 也是一些机器似乎以另一种速率运行的原因。 winmm.dll提供的Windows多媒体API可以让你修补它。

你不想做的是使用秒表。 当您在热循环中使用它时,您只能从中获得准确的间隔测量值,该循环不断检查间隔是否已经过去。 Windows在量程到期时不客气地对待这样的线程,当其他线程竞争处理器时,线程将不会重新调度运行一段时间。 这种效果很容易被忽略,因为您通常不会使用其他正在运行的进程和烧录CPU时间来调试代码。

您要使用的函数是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()。

您可能想要尝试StopWatch ,它使用与DirectX相同的高性能计时器。

请注意,为什么15ms是有效的最小Sleep时间的部分原因是它可以花费这个数量级实际交换您的过程,交换另一个,让它有一些时间做任何事情,交换它并交换你的再次。 假设你有多个内核,专门用一个100Hz更新器可能是最好的,特别是如果你知道什么时候你想跳过几个周期来手动生成垃圾收集,和/或按照建议使用C / C ++。

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毫秒,即100HZ,平均得到0.0109 ...几乎是现场!