9

高頻度のコールバック スレッドを作成しようとしています。基本的に、定期的な高周波 (最大 100Hz) 間隔で実行する関数が必要です。Windows の通常のスレッド実行スライスは ~15ms です。15ms よりも速くなる定期的な間隔を指定したいと思います。

これが私が達成しようとしていることです。特定の間隔でメッセージを送信する必要がある外部デバイスがあります。間隔は状況により変動します。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 署名。 ピンヴォーク リンク

[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 を使用します。カスタム キューも作成できるため、これはある程度柔軟です。ただし、単一のインスタンスのみが必要な場合の最も簡単な実装は、デフォルト キューを使用することです。

ストップウォッチとスピン ループを使用して、このソリューションをサイド 1 と一緒にテストしましたが、タイミングに関して受け取った結果はほぼ同じでした。ただし、私のマシンでは CPU 負荷が大幅に異なりました。

スピンループ付きストップウォッチ - ~12-15% の一定の CPU 負荷 (私のコアの約 50%) CreateTimerQueueTimer - ~3-4% の一定の CPU 負荷

また、CreateTimerQueueTimer オプションを使用すると、コードのメンテナンスが軽減されると思います。コード フローにロジックを追加する必要がないためです。

4

4 に答える 4

11

リンクやコメントを通じて、多くの不正確な情報が拡散されていますはい、デフォルトでは、ほとんどのマシンでクロックティック割り込みは 1/64 秒 = 15.625 ミリ秒ですが、変更できます。また、一部のマシンが別の速度で動作しているように見える理由もあります。winmm.dll から入手できる Windows マルチメディア API を使用すると、それをいじることができます。

やりたくないことは、ストップウォッチを使用することです。間隔が経過したかどうかを常にチェックするホットループで使用する場合にのみ、正確な間隔測定値が得られます。Windows は、クォンタムが期限切れになると、そのようなスレッドを不親切に扱います。他のスレッドがプロセッサをめぐって競合する間、スレッドは再スケジュールされません。通常、他のプロセスがアクティブに実行され、CPU 時間を消費している状態でコードをデバッグすることはないため、この影響は見逃されがちです。

使用する関数は timeSetEvent() です。これは、1 ミリ秒まで短縮できる非常に正確なタイマーを提供します。スケジューリングの制約により前のコールバックが遅延したときに追いつくために、必要に応じて (そして可能であれば) 間隔を短縮して自己修正します。ただし、System.Threading.Timer と同様に、コールバックはスレッドプールスレッドから作成されるため、使いにくいことに注意してください。安全なインターロックを使用し、再入の問題が発生しないように対策を講じてください。

完全に異なるアプローチは timeBeginPeriod() で、クロック割り込みレートを変更します。1 つの 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 秒も得られません。これを処理するのは非常に厄介です。1 ミリ秒のレートを要求して 10 秒間スリープすることによってのみ、真に安全にすることができます。バッテリ駆動のマシンで実行しないでください。timeSetEvent() を優先してください。

于 2013-05-19T08:48:11.070 に答える
1

DirectX で使用されているのと同じ高性能タイマーを使用するStopWatchを試してみることをお勧めします。

于 2013-05-19T00:26:42.523 に答える
0

15 ミリ秒が有効な最小時間である理由の一部は、Sleep実際にプロセスをスワップアウトし、別のプロセスをスワップインし、何かを行う時間を確保し、スワップアウトし、自分のプロセスをスワップインするには、その桁数がかかる可能性があることに注意してください。また。複数のコアがあると仮定すると、手動で生成されたガベージ コレクションのために数サイクルをスキップしたい場合や、提案どおりに C/C++ を使用したい場合は特に、1 つを 100Hz アップデーター専用にするのがおそらく最適です。

于 2013-05-19T06:53:37.413 に答える