问题描述
我正在寻找创建一个高频回调线程.本质上,我需要一个以常规高频(高达 100Hz)间隔执行的函数.我意识到windows有一个正常的线程执行切片是~15ms.我想指定一个可以快于 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 调用方式
WINAPI call via
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
我也觉得使用 CreateTimerQueueTimer 选项可以减少代码维护.因为它不需要将逻辑添加到您的代码流中.
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.
推荐答案
通过链接和评论传播了很多不准确的信息.是的,默认情况下,大多数机器上的时钟滴答中断是 1/64 秒 = 15.625 毫秒,但可以更改.也是某些机器似乎以另一种速率运行的原因.可从 winmm.dll 获得的 Windows 多媒体 api 让您可以修改它.
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 对这样的线程在其时间期限到期时不友好地对待,当其他线程竞争处理器时,该线程将不会被重新调度运行一段时间.这种效果很容易被忽略,因为您通常不会在其他进程积极运行并消耗 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 毫秒.它是自我纠正的,如果有必要(并且可能)减少间隔以赶上前一个回调由于调度限制而延迟.但是要注意它很难使用,回调是由线程池线程进行的,类似于 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 毫秒,您将获得中断率.一些代码可以演示它的工作方式:
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);
}
我的机器上的输出:
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().
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().
这篇关于高频时序.NET的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!