15

両方とも、要求されたものとはかなり異なる間隔で発砲しますSystem.Timers.TimerSystem.Threading.Timer例えば:

new System.Timers.Timer(1000d / 20);

1 秒あたり 20 回ではなく、16 回起動するタイマーが生成されます。

長すぎるイベント ハンドラーによる副作用がないことを確認するために、次の小さなテスト プログラムを作成しました。

int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
    int count = 0;

    // Initialize timer
    System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
    timer.Elapsed += delegate { Interlocked.Increment(ref count); };

    // Count for 10 seconds
    DateTime start = DateTime.Now;
    timer.Enabled = true;
    while (DateTime.Now < start + TimeSpan.FromSeconds(10))
        Thread.Sleep(10);
    timer.Enabled = false;

    // Calculate actual frequency
    Console.WriteLine(
        "Requested frequency: {0}\nActual frequency: {1}\n",
        frequency, count / 10d);
}

出力は次のようになります。

要求: 5 Hz; 実際: 4,8 Hz
要求: 10 Hz; 実際: 9,1 Hz
要求: 15 Hz; 実際: 12,7 Hz
要求: 20 Hz; 実際: 16 Hz
要求: 30 Hz; 実際: 21,3 Hz
要求: 50 Hz; 実際: 31,8 Hz
要求: 75 Hz; 実際: 63,9 Hz
要求: 100 Hz; 実際: 63,8 Hz
要求: 200 Hz; 実際: 63,9 Hz
要求: 500 Hz; 実際: 63,9 Hz

実際の周波数は、要求された周波数から最大 36% ずれています。(そして明らかに 64 Hz を超えることはできません。) Microsoft が よりも「精度が高い」ためにこのタイマーを推奨していることを考えるとSystem.Windows.Forms.Timer、これは私を困惑させます。

ところで、これらはランダムな偏差ではありません。それらは毎回同じ値です。また、他のタイマー クラス の同様のテスト プログラムSystem.Threading.Timerは、まったく同じ結果を示します。

私の実際のプログラムでは、1 秒あたり正確に 50 サンプルで測定値を収集する必要があります。これにはまだリアルタイム システムは必要ありません。また、1 秒あたり 50 サンプルではなく 32 サンプルを取得するのは非常にイライラします。

何か案は?

@クリス:そうです、間隔はすべて1/64秒前後の整数倍のようです。ところで、イベント ハンドラに Thread.Sleep(...) を追加しても違いはありません。がスレッド プールを使用することを考えると、これは理にかなってSystem.Threading.Timerいます。そのため、各イベントはフリー スレッドで発生します。

4

10 に答える 10

23

winmm.dll を使用すると、より多くの CPU 時間を使用できますが、より適切に制御できます。

winmm.dll タイマーを使用するように変更した例を次に示します。

const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";

delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);

[DllImport(WINMM)]
static extern uint timeSetEvent(
      UInt32            uDelay,      
      UInt32            uResolution, 
      [MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,  
      UInt32            dwUser,      
      Int32             fuEvent      
    );

[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);

// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);

static long CPUFrequency;

static int count;

static void Main(string[] args)
{            
    QueryPerformanceFrequency(out CPUFrequency);

    int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

    foreach (int freq in frequencies)
    {
        count = 0;

        long start = GetTimestamp();

        // start timer
        uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);

        // wait 10 seconds
        while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
        {
            Thread.Sleep(1);
        }

        // end timer
        timeKillEvent(timerId);

        Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
    }

    Console.ReadLine();
}

static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
    Interlocked.Increment(ref count);
}

static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
    return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}

static public long GetTimestamp()
{
    long result;
    QueryPerformanceCounter(out result);
    return result;
}

そして、ここに私が得る出力があります:

Requested frequency: 5
Actual frequency: 5

Requested frequency: 10
Actual frequency: 10

Requested frequency: 15
Actual frequency: 15

Requested frequency: 20
Actual frequency: 19

Requested frequency: 30
Actual frequency: 30

Requested frequency: 50
Actual frequency: 50

Requested frequency: 75
Actual frequency: 76

Requested frequency: 100
Actual frequency: 100

Requested frequency: 200
Actual frequency: 200

Requested frequency: 500
Actual frequency: 500

お役に立てれば。

于 2009-04-01T00:44:45.280 に答える
5

これらのクラスは、リアルタイムでの使用を意図したものではなく、Windows などのオペレーティング システムの動的スケジューリングの性質の影響を受けます。リアルタイムの実行が必要な場合は、おそらくいくつかの組み込みハードウェアを見たいと思うでしょう。100% 確信があるわけではありませんが、.netcpu はチップ上の小さな .NET ランタイムのリアルタイム バージョンである可能性があると思います。

http://www.arm.com/markets/emerging_applications/armpp/8070.html

もちろん、これらの間隔に関連付けられたコードが非リアルタイム オペレーティング システムで実行される場合、これらの間隔の精度がどれほど重要かを評価する必要があります。もちろん、これが純粋に学術的な質問でない限り (その場合 - はい、興味深いです! :P)。

于 2009-01-06T13:50:12.497 に答える
4

実際のタイマー周波数は 63.9 Hz またはその整数倍のようです。

これは、約 15 ミリ秒 (またはその整数倍、つまり 30 ミリ秒、45 ミリ秒など) のタイマー分解能を意味します。

これは、'tick' の整数倍に基づくタイマーであることが予想されます (たとえば、DOS では、'tick' 値は 55 ミリ秒 / 18 Hz でした)。

ティック数が 15 ミリ秒ではなく 15.65 メックである理由がわかりません。実験として、タイマー ハンドラー内で数ミリ秒スリープするとどうなるでしょうか。ティック間に 15 ミリ秒が表示され、タイマー ハンドラーでは各ティックで 0.65 ミリ秒が表示されるでしょうか?

于 2009-01-06T14:01:43.767 に答える
3

Windows (およびその上で実行される .NET) は、プリエンプティブなマルチタスク オペレーティング システムです。特定のスレッドはいつでも別のスレッドによって停止される可能性があり、プリエンプションを実行しているスレッドが適切に動作しない場合、必要なときに制御を取り戻すことはできません。

簡単に言えば、これが正確なタイミングを保証できない理由であり、Windows と .NET が特定の種類のソフトウェアに適していないプラットフォームである理由です。必要なときに正確に制御できないために命が危険にさらされている場合は、別のプラットフォームを選択してください。

于 2009-01-06T14:01:52.347 に答える
3

実際には最大 100 Hz の異なる数値を取得していますが、大きな偏差がいくつかありますが、ほとんどの場合、要求された数値に近くなっています (最新の .NET SP で XP SP3 を実行しています)。

System.Timer.Timer は System.Threading.Timer を使用して実装されているため、同じ結果が得られる理由はこれで説明できます。タイマーは、ある種のスケジューリング アルゴリズムなどを使用して実装されていると思います (これは内部呼び出しです。Rotor 2.0 を見ると、いくつかの光が当たる可能性があります)。

Sleep とコールバックを呼び出す別のスレッド (またはその組み合わせ) を使用して、一種のタイマーを実装することをお勧めします。ただし、結果についてはわかりません。

それ以外の場合は、マルチメディア タイマー(PInvoke) を参照してください。

于 2009-01-06T14:14:46.253 に答える
2

リアルタイム環境にジャンプする必要がある場合は、過去に (カスタム シリアル デバイスからの) 決定論的サンプリングが必要なときに RTX を使用しましたが、非常にうまくいきました。

http://www.pharlap.com/rtx.htm

于 2009-01-06T14:36:26.210 に答える
2

これは、マルチメディアタイマーを使用したタイマーの優れた実装です http://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

于 2010-02-12T02:34:32.973 に答える
1

タイマーがオフになる理由はたくさんあります。ハードウェアの例、同じスレッドが別の処理でビジー状態など...

より正確な時間が必要な場合は、System.Diagnostics 名前空間の Stopwatch クラスを使用します。

于 2009-01-06T13:42:12.963 に答える
0

あなたのコメントに基づいて、あなたはタイマーをまったく使うべきではありません。クォンタムを失わないように、間隔とスピンロックをチェックするためにストップウォッチ付きのループを使用する必要があります。

于 2009-04-01T00:48:50.400 に答える
0

問題の一部は、タイマーが考慮すべき2 つの遅延があることです。これは、タイマーが OS にどのように実装されているかによって異なります。

  1. お待ちいただく時間
  2. #1 が発生してからプロセスがスケジューリング キューで順番を取得するまでの時間。

タイマーは #1 を適切に制御できますが、#2 はほとんど制御できません。再度実行したいという信号を OS に送ることができますが、OS は必要に応じて自由に起動できます。

于 2009-01-06T14:18:39.113 に答える