高频率定时.NET高频率、NET

2023-09-02 11:54:27 作者:苏:苏门打蜡、苏打水、读苏使人进步

我希望创建一个高频率的回调线程。基本上我需要一个函数在例行高频执行(最高至100Hz)的时间间隔。我知道Windows有一个正常的线程执行切片〜15毫秒。我想指定一个固定时间间隔,可以比15毫秒快得多。

I'm looking to create a high frequency callback thread. Essentially I need a function to execute at a regular high frequency (up to 100Hz) interval. I realize that windows has a normal thread execution slice is ~15ms. I would like to specify a regular interval that can be faster than 15ms.

这是我想要完成的任务。予有需要在一定的时间间隔,以传递消息的外部设备。间隔是可变的视情况而定。我希望我不会永远比100Hz的(10ms)的信息传输速率需要更多的。

This is what I'm trying to accomplish. I have an external device that needs to be messaged at a certain interval. The interval is variable depending on the situation. I expect that I would not ever need more than a 100Hz (10ms) message rate.

我当然可以实现旋转环,但是,我希望有是不会要求那么多浪费资源的解决方案。

I can of course implement a spin loop, however, I was hoping there is a solution that would not require so much wasted resources.

所提供的链接的问题/解答不解决这个问题。虽然我同意,这个问题已经被问了几个不同的方式,但一直没有一个很好的解决方案,真正解决了这个问题。

The provided links to questions/answers do not resolve this question. While I agree that the question has been asked several different ways, there has not been a good solution that actually solved the problem.

大多数提供通话用秒表和手动执行定时任务的答案这完全是太CPU密集型。唯一可行的解​​决办法是使用多媒体定时器,其中有一对夫妇的缺陷作为Haans提及。我已经发现了另一个解决方案,然而,我会添加如下。我不知道的陷阱,在这个时候,但我打算做一些测试和研究。我仍然有兴趣在有关解决方案的意见。

Most of the answers provided talk to using Stopwatch and performing the timing task manually which is entirely too CPU intensive. The only viable solutions were using the multimedia timers which had a couple pitfalls as Haans mentioned. I have found another solution however that I'll add below. I do not know of the pitfalls at this time but I plan to do some testing and research. I am still interested in comments regarding the solution.

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做到这一点。还有然而与多媒体计时器时处理这也将是必需的。

Link - http://msdn.microsoft.com/en-us/library/windows/desktop/ms682485%28v=vs.85%29.aspx I'm using PInvoke to accomplish this. This would also be required when dealing with the multimedia timers as well however.

我的PInvoke签名为那些有兴趣。 PInvoke的链接

My PInvoke signature for those interested. 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停止计时器回调。这有点灵活,因为你可以创建自定义的队列为好。但是,最简单的实现,如果只有一个实例是需要将​​是使用默认队列。

use CreateTimerQueueTimer to start a timer callback. Use DeleteTimerQueueTimer to stop the timer callback. This is somewhat flexible as you can create custom queues as well. However, the easiest implementation if only a single instance is needed would be to use the default queue.

我测试沿侧面有一个用秒表带旋转环和我的问候定时接收几乎相同的结果,此解决方案。然而,CPU负载是我的机器上显著不同。

I tested this solution along side one using the Stopwatch with a spin loop and the results I received in regards to timing were nearly identical. However, the CPU Load was significantly different on my machine.

秒表与自旋循环 - 〜12-15%不变的CPU负载(约50%我的内核之一) CreateTimerQueueTimer - 〜3-4%不变的CPU负载

Stopwatch with spin loop - ~12-15% constant CPU load (About 50% of one of my cores) CreateTimerQueueTimer - ~3-4% constant CPU load

我也感觉到了code维护将使用CreateTimerQueueTimer选项减少。因为它不要求逻辑被添加到你的code流动。

I also feel the code maintenance will be reduced using the CreateTimerQueueTimer option. as it doesn't require logic to be added to your code flow.

推荐答案

还有就是通过链接和评论不准确的信息S $ P $垫的很多的。是的,在默认情况下,时钟节拍中断是1/64秒= 15.625毫秒在大多数机器上,但它是可以改变的。此外,一些机器出现在另一个速率工作的原因。在Windows多媒体API,可从WINMM.DLL让你鼓捣出来。

There is a lot of inaccurate information spread through the links and comments. Yes, by default the clock tick interrupt is 1/64 seconds = 15.625 msec on most machines but it can be changed. Also the reason that some machines appear to operate on another rate. The Windows multi-media api, available from winmm.dll lets you tinker with it.

您的有什么不的想使用一个秒表做。你只会得到一个精确的间隔测量出来的,当你在一个热循环,不断检查间隔已过使用它。 Windows将这样的线程不友好时其量子到期时,该线程将不会被重新安排到了一会儿时运行其他线程竞争的处理器。这种效果是很容易错过,因为你通常不会调试您的code与其他进程的积极跑动和燃烧的CPU时间。

What you don't want to do is using a Stopwatch. You'll only get an accurate interval measurement out of it when you use it in a hot loop that constantly check if the interval has passed. Windows treats such a thread unkindly when its quantum expires, the thread won't be re-scheduled to run for a while when other threads compete for the processor. This effect is easy to miss since you don't typically debug your code with other processes actively running and burning cpu time.

您想要使用的功能是timeSetEvent(),它提供了一个高度精确的计时器,可以去低至1毫秒。它是自我纠正,减少间隔(如有必要和可能的)追上时,previous回调被推延由于日程安排的限制。但要小心,这是很难用,回调是从一个线程池线程,类似于System.Threading.Timer制成,所以一定要使用安全联锁,并采取应对措施,以确保你不会得到麻烦来自重入。

The function you want to use is timeSetEvent(), it provides a highly accurate timer that can go as low as 1 millisecond. It is self-correcting, reducing the interval if necessary (and possible) to catch up when the previous callback got delayed due to scheduling constraints. Beware however that it is difficult to use, the callback is made from a threadpool thread, similar to System.Threading.Timer, so be sure to use safe interlocking and take countermeasures that ensure that you won't get trouble from re-entrancy.

一个完全不同的方法是timeBeginPeriod(),它改变时钟中断率。具有许多副作用,对于一个Thread.sleep代码()变得更精确。这往往是简单的解决方案,因为你可以使同步。刚睡了1毫秒,你会得到的中断率。有些code与播放演示它的工作方式:

A completely different approach is timeBeginPeriod(), it alters the clock interrupt rate. That has many side-effects, for one Thread.Sleep() becomes more accurate. Which tends to be the easier solution since you can make that synchronous. Just sleep for 1 msec and you'll get the interrupt rate. Some code to play with that demonstrates the way it works:

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);
}

输出我的机器上:

Output on my machine:

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毫秒,那么你将不会有第二次了这个code。这是非常尴尬的处理,只能使之真正安全的问了1毫秒率,睡了10.不要运行的电池驱动的机器上。不要有利于timeSetEvent()。

Do beware however of a problem with this approach, if another process on your machine has already lowered the clock interrupt rate below 10 msec then you will not get a second out of this code. That's very awkward to deal with, you can only make it truly safe by asking for a 1 msec rate and sleeping for 10. Don't run that on a battery operated machine. Do favor timeSetEvent().