4

メイン処理ジョブがフリーズしたかどうかを確認するために使用される「ウォッチドッグ」タイマーを含むプロダクション サービスに問題があります (これは、残念ながらテストで再現できない COM 相互運用の問題に関連しています)。

現在の仕組みは次のとおりです。

  • 処理中、メイン スレッドは をリセットしManualResetEvent、1 つのアイテムを処理し (これにはそれほど時間はかかりません)、イベントを設定します。その後、残りのアイテムの処理を続行します。
  • 5 分ごとに、ウォッチドッグがWaitOne(TimeSpan.FromMinutes(5))このイベントを呼び出します。結果が false の場合、サービスは再起動されます。
  • 通常の操作中に、処理に 5 分ほどかからない場合でも、このウォッチドッグによってサービスが再起動されることがあります。

複数のアイテムが処理を待っている場合Set()、最初のアイテムが処理されてReset()から 2 番目のアイテムが処理されるまでの時間が短すぎWaitOne()て、イベントが設定されていることを認識していないように見えることが原因であると思われます。

私の理解でWaitOne()は、ブロックされたスレッドはが呼び出されたときにシグナルを受信することが保証されていSet()ますが、何か重要なものが欠けていると思います。

Thread.Sleep(0)を呼び出した後に呼び出してコンテキストの切り替えを許可するとSet()WaitOne()決して失敗しないことに注意してください。

以下に含まれているのは、私の製品コードと同じ動作を生成するサンプルです。が800 ミリ秒ごとに呼び出されているにもかかわらずWaitOne()、5 秒間待機して失敗することがあります。Set()

private static ManualResetEvent _handle;

private static void Main(string[] args)
{
    _handle = new ManualResetEvent(true);

    ((Action) PeriodicWait).BeginInvoke(null, null);
    ((Action) PeriodicSignal).BeginInvoke(null, null);

    Console.ReadLine();
}

private static void PeriodicWait()
{
    Stopwatch stopwatch = new Stopwatch();

    while (true)
    {
        stopwatch.Restart();
        bool result = _handle.WaitOne(5000, false);
        stopwatch.Stop();
        Console.WriteLine("After WaitOne: {0}. Waited for {1}ms", result ? "success" : "failure",
                            stopwatch.ElapsedMilliseconds);
        SpinWait.SpinUntil(() => false, 1000);
    }
}

private static void PeriodicSignal()
{
    while (true)
    {
        _handle.Reset();
        Console.WriteLine("After Reset");
        SpinWait.SpinUntil(() => false, 800);
        _handle.Set();
        // Uncommenting either of the lines below prevents the problem
        //Console.WriteLine("After Set");
        //Thread.Sleep(0);
    }
}

上記のコードの出力


質問

Set()密接に続いて呼び出すことReset()は、ブロックされたすべてのスレッドが再開されることを保証しないことを理解していますが、待機中のスレッドが解放されることも保証されません?

4

2 に答える 2

11

いいえ、これは根本的に壊れたコードです。このように短時間 MRE を設定したままにしておくと、WaitOne() が完了する可能性はほとんどありません。Windows は、イベントでブロックされたスレッドを解放することを好みます。ただし、スレッドが待機していない場合、これは大幅に失敗します。または、スケジューラは代わりに、より高い優先度で実行され、ブロックも解除された別のスレッドを選択します。たとえば、カーネルスレッドである可能性があります。MRE は、通知されてまだ待機していないという「記憶」を保持しません。

Sleep(0) も Sleep(1) も、待機が完了することを保証するには十分ではありません。待機中のスレッドがスケジューラによってバイパスされる頻度に合理的な上限はありません。10秒以上かかる場合は、おそらくプログラムをシャットダウンする必要がありますが;)

これを別の方法で行う必要があります。簡単な方法は、ワーカーに依存して最終的にイベントを設定することです。したがって、待機を開始する前にリセットしてください。

private static void PeriodicWait() {
    Stopwatch stopwatch = new Stopwatch();

    while (true) {
        stopwatch.Restart();
        _handle.Reset();
        bool result = _handle.WaitOne(5000);
        stopwatch.Stop();
        Console.WriteLine("After WaitOne: {0}. Waited for {1}ms", result ? "success" : "failure",
                            stopwatch.ElapsedMilliseconds);
    }
}

private static void PeriodicSignal() {
    while (true) {
        _handle.Set();
        Thread.Sleep(800);   // Simulate work
    }
}
于 2013-03-20T01:30:47.913 に答える
6

このような OS イベントを「パルス」することはできません。

他の問題の中でも特に、OS ハンドルでブロッキング待機を実行している OS スレッドは、カーネルモード APC によって一時的に中断される可能性があるという事実があります。APC が終了すると、スレッドは待機を再開します。その中断中にパルスが発生した場合、スレッドはそれを認識しません。これは、「パルス」が見落とされる可能性がある一例にすぎません ( Windows での並行プログラミング、ページ 231で詳細に説明されています)。

ところで、これはPulseEventWin32 API が完全に壊れていることを意味します。

マネージド スレッドを使用する .NET 環境では、パルスを見逃す可能性がさらに高くなります。ガベージコレクションなど

AutoResetEventあなたの場合、作業プロセスによって繰り返し実行され、完了Setするたびにウォッチドッグプロセスによって(自動的に)リセットされる に切り替えることを検討Waitします。また、ウォッチドッグを 1 分おきにチェックするだけにして、ウォッチドッグを「手なずけ」たいと思うでしょう。

于 2013-03-20T01:38:07.313 に答える