高频时序.NET

时间:2013-05-19 00:12:13

标签: c# .net multithreading synchronization

我正在寻找创建一个高频回调线程。基本上我需要一个以常规高频(高达100Hz)间隔执行的功能。我意识到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 link

[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选项可以减少代码维护。因为它不需要将逻辑添加到您的代码流中。

4 个答案:

答案 0 :(得分:10)

通过链接和评论传播的很多信息不准确。是的,默认情况下,大多数机器上的时钟节拍中断是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()。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

请注意,15毫秒是有效最短Sleep时间的部分原因是它可以采用这个数量级实际交换您的流程,交换另一个,让它有一些时间做任何事情,换掉它,然后再交换你的。假设你有多个内核,那么专用一个内核可能是最好的,特别是如果你知道什么时候你想跳过几个周期来手动生成垃圾收集,和/或按照建议使用C / C ++。

答案 3 :(得分:0)

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 avg。 0.00197391928071928

所以间隔不到2毫秒!我想如果有更多的争论可能会很快上升,虽然我确实有很多应用程序打开,但没有什么可以磨掉的。

睡觉时间为10毫秒,即100HZ,平均得分为0.0109 ......非常适合拍摄!