首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >高频定时.NET

高频定时.NET
EN

Stack Overflow用户
提问于 2013-05-19 00:12:13
回答 4查看 2.5K关注 0票数 9

我希望创建一个高频回调线程。本质上,我需要一个功能来执行一个正常的高频(高达100赫兹)间隔。我意识到windows有一个正常的线程执行片,大约是15 is。我想指定一个可以比15 be更快的规则间隔。

这就是我想要达到的目标。我有一个外部设备,需要在一定的时间间隔内发送消息。间隔是可变的,视情况而定。我希望我不会需要超过100赫兹(10毫秒)的消息率。

当然,我可以实现一个spin循环,但是,我希望有一个解决方案,不会需要那么多浪费资源。

提供的问题/答案链接不能解决此问题。虽然我同意这个问题被问了几个不同的方法,但并没有一个好的解决办法来真正解决这个问题。

大多数答案提供了关于使用秒表和手动执行计时任务的谈话,这完全是CPU密集型的。唯一可行的解决方案是使用多媒体计时器,正如Haans提到的那样,这种计时器有几个陷阱。不过,我已经找到了另一个解决方案,我将在下面添加。我不知道目前的陷阱,但我计划做一些测试和研究。我仍然对有关解决办法的评论感兴趣。

WINAPI调用

代码语言:javascript
复制
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
);

代码语言:javascript
复制
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链接

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-05-19 08:48:11

有许多不准确的信息传播通过链接和评论。是的,在大多数机器上,默认情况下时钟滴答中断为1/64秒= 15.625毫秒,但它可以更改。还有一些机器似乎以另一种速度运转的原因。winmm.dll提供的Windows多媒体api允许您对其进行修补。

你不想做的就是用秒表。只有在不断检查间隔是否过去的热循环中使用它时,才能得到准确的间隔度量。当一个线程的量程到期时,Windows将不友好地对待它,当其他线程争夺处理器时,线程将不会在一段时间内重新调度运行。这种效果很容易被忽略,因为您通常不使用其他进程来调试代码,而是积极地运行和燃烧cpu时间。

您想要使用的函数是timeSetEvent(),它提供了一个非常精确的计时器,可以低到1毫秒。它是自校正的,如果有必要(并且可能的话),当上一次回调由于调度限制而延迟时,可以缩短时间间隔。但是,请注意,回调是从线程池线程(类似于System.Threading.Timer )发出的,因此要确保使用安全的联锁,并采取对策,以确保不会因为重新进入而带来麻烦。

另一种完全不同的方法是timeBeginPeriod(),它改变时钟中断率。这有许多副作用,因为一个Thread.Sleep()变得更准确了。这往往是更容易的解决方案,因为您可以使这个同步。只要睡上1毫秒,你就能得到中断率。一些要使用的代码演示了它的工作方式:

代码语言:javascript
复制
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);
}

我的机器上的输出:

代码语言:javascript
复制
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 m秒以下,那么您将无法从这段代码中得到一秒钟。这是非常尴尬的处理,你只能让它真正的安全,要求1毫秒的速度和睡眠10。不要运行在电池操作的机器。请支持timeSetEvent()。

票数 11
EN

Stack Overflow用户

发布于 2013-05-19 00:26:42

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

票数 1
EN

Stack Overflow用户

发布于 2013-05-19 06:53:37

请注意,15 is是有效最小Sleep时间的部分原因是,它可以用这个数量级来实际交换您的进程,交换另一个进程,让它有时间做任何事情,交换掉它,然后再交换您的进程。假设您有多个核心,那么使用一个来手动更新100 as可能是最好的,特别是当您想跳过几个手动生成的垃圾收集周期和/或按照建议使用C/C++的时候。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16630238

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档