高频率定时.NET(high frequency timing .NET)

2019-09-16 06:40发布

我期待创造一个高频率的回调线程。 基本上我需要以规则的高频来执行(最高至100Hz)间隔的函数。 我认识到,Windows有一个正常的线程执行片是15毫秒〜。 我想指定一个固定的间隔可以比15毫秒更快。

这就是我试图完成。 我有一个需要在一定的时间间隔,以传递消息的外部设备。 间隔根据情况是可变的。 我希望我不会永远需要超过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选项可减少。 因为它并不需要逻辑被添加到您的代码流。

Answer 1:

很多通过链接和评论传播不准确的信息。 是的,默认情况下,时钟节拍中断是1/64秒=在大多数机器15.625毫秒,但它是可以改变的。 还有些机器会显示在另一个速率运行的原因。 在Windows多媒体API,可从WINMM.DLL让你用它修补。

什么你不想做用秒表。 当你在一个热循环,如果时间间隔已经通过了经常检查使用它,你只会得到一个准确的间隔测量出来。 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()。



Answer 2:

你可能会想尝试秒表 ,使用由DirectX的使用相同的高科技性能的定时器。



Answer 3:

注意:为什么为15ms的最小有效的原因,部分Sleep时间,它可以采取幅度是为了实际交换你的进程外,在换另一个,让它有时间做任何事情,交换出去,换你在一次。 假设你有多个内核,专一到handingly你100Hz的更新可能是最好的,特别是如果你知道,当你想跳过几个周期对于手动生成的垃圾收集,和/或使用C / C ++的建议。



Answer 4:

Thread.Sleep()似乎并不像不准确的,因为你可能认为在这样小的间隔,在Windows 8.1 x64的酷睿i7笔记本.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 ......在几乎当场!



文章来源: high frequency timing .NET