我正在寻找创建一个高频回调线程。基本上我需要一个以常规高频(高达100Hz)间隔执行的功能。我意识到Windows有一个正常的线程执行切片是〜15ms。我想指定一个可以快于15ms的常规间隔。
这就是我想要完成的事情。我有一个需要以一定间隔发送消息的外部设备。间隔根据情况而变化。我希望我不需要超过100Hz(10ms)的消息率。
我当然可以实现自旋循环,但是,我希望有一个解决方案不需要浪费太多资源。
提供的问题/答案链接无法解决此问题。虽然我同意这个问题有几种不同的问题,但实际上并没有一个好的解决方案可以解决问题。
提供的大多数答案都与使用秒表和手动执行计时任务有关,这完全是CPU密集型的。唯一可行的解决方案是使用多媒体定时器,有一些陷阱,正如Haans所说。我找到了另一种解决方案,但我将在下面添加。我不知道此时的陷阱,但我打算做一些测试和研究。我仍然对有关解决方案的评论感兴趣。
通过
调用WINAPIBOOL 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选项可以减少代码维护。因为它不需要将逻辑添加到您的代码流中。
答案 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 ......非常适合拍摄!