.NETでスレッドを正常に終了する方法についての質問 質問する

.NETでスレッドを正常に終了する方法についての質問 質問する

私は、このトピックについて読んだ多数の記事から、Thread.Abort() が悪質であることを理解しているので、現在、すべての abort を削除して、よりクリーンな方法で置き換える作業を行っています。また、stackoverflow でユーザーの戦略を比較した後、方法: スレッドの作成と終了 (C# プログラミング ガイド)MSDN では、どちらも非常によく似たアプローチを述べています。つまり、volatile boolアプローチ チェック戦略を使用するというものです。これは良いのですが、まだいくつか疑問があります...

ここですぐに目につくのは、単に処理コードのループを実行するだけの単純なワーカー プロセスがない場合はどうするかということです。たとえば、私の場合、プロセスはバックグラウンド ファイル アップローダー プロセスで、実際に各ファイルをループします。これは問題ありません。もちろん、while (!_shouldStop)ループの反復をすべてカバーする を先頭に追加することもできますが、次のループの反復に達する前に発生するビジネス プロセスが他にもたくさんあります。このキャンセル プロシージャはすばやく実行する必要があります。ワーカー関数全体にわたって、4 行から 5 行ごとにこれらの while ループを散りばめる必要があるとは言わないでください。

もっと良い方法があることを心から願っています。これが実際に正しい(そして唯一の?)アプローチなのか、あるいは私が求めているものを達成するために過去に使用された戦略なのか、どなたかアドバイスをいただけないでしょうか。

ありがとう、みんな。

参考文献:これらすべてのSOの応答ワーカー スレッドがループすると想定します。これは納得できません。線形ではあるがタイムリーなバックグラウンド操作の場合はどうなるでしょうか。

ベストアンサー1

残念ながら、これより良い選択肢はないかもしれません。それは、実際のシナリオによって異なります。スレッドを安全なポイントで適切に停止するという考え方です。これが、Thread.Abort安全ポイントで発生することが保証されていないため、良くない理由の核心です。コードに停止メカニズムを散りばめると、安全ポイントを効果的に手動で定義できます。これは、協調キャンセルと呼ばれます。これを行うには、基本的に 4 つの広範なメカニズムがあります。状況に最も適したものを選択できます。

停止フラグをポーリングする

この方法については既に触れましたね。これはかなり一般的な方法です。アルゴリズムの安全なポイントでフラグを定期的にチェックし、シグナルが送られたら終了します。標準的な方法は、変数を としてマークすることですvolatile。それが不可能または不便な場合は、 を使用できますlock。ローカル変数を としてマークすることはできないvolatileので、たとえばラムダ式がクロージャを介してそれをキャプチャする場合は、必要なメモリバリアを作成するために別の方法に頼る必要があります。この方法については、他に言うべきことはあまりありません。

TPLの新しいキャンセルメカニズムを使用する

これは停止フラグのポーリングに似ていますが、TPL の新しいキャンセル データ構造を使用する点が異なります。これは依然として協調キャンセル パターンに基づいています。 を取得し、 がCancellationToken定期的に をチェックする必要があります。キャンセルを要求するには、トークンを最初に提供したをIsCancellationRequested呼び出します。新しいキャンセル メカニズムを使用してできることはたくさんあります。詳細については、CancelCancellationTokenSourceここ

待機ハンドルを使用する

このメソッドは、ワーカー スレッドが通常の操作中に特定の間隔またはシグナルを待機する必要がある場合に役立ちます。たとえば、 を実行して、スレッドに停止する時刻を知らせることができますSet。イベントがシグナルされたかどうかを示す を返す関数ManualResetEventを使用して、イベントをテストできます。は、その時間内にイベントがシグナルされなかった場合に、 の呼び出しが戻るのを待機する時間を指定するパラメーターを受け取ります。 の代わりにこの手法を使用して、停止表示を同時に取得できます。また、スレッドが待機する必要がある可能性のある他のインスタンスがある場合にも役立ちます。 を呼び出して、停止イベントを含むすべてのイベントを 1 回の呼び出しで待機できます。イベントを使用すると、 を呼び出すよりも優れた方法になる場合があります。プログラムのフローをより細かく制御できるためです (は例外をスローするため、必要なクリーンアップを実行するためにブロックを戦略的に配置する必要があります)。WaitOneboolWaitOneThread.SleepWaitHandleWaitHandle.WaitAnyThread.InterruptThread.Interrupttry-catch

特殊なシナリオ

非常に特殊な停止メカニズムを持つ、1 回限りのシナリオがいくつかあります。それらすべてを列挙することは、この回答の範囲外であることは間違いありません (ほぼ不可能であることは気にしないでください)。ここで私が言っていることの良い例は、クラスです。スレッドがまたはSocketの呼び出しでブロックされている場合、呼び出しは、ブロックされていた呼び出しでソケットを中断し、実質的にブロックを解除します。BCL には、スレッドのブロックを解除するために同様の手法を使用できる領域が他にもいくつかあると確信しています。SendReceiveClose

スレッドを中断するにはThread.Interrupt

ここでの利点は、それが単純で、コードに何かを散りばめることに重点を置く必要がないことです。欠点は、アルゴリズム内のセーフ ポイントの位置をほとんど制御できないことです。その理由は、 が、Thread.InterruptBCL ブロッキング呼び出しの 1 つに例外を挿入することによって機能するためです。これにはThread.Sleep、、、WaitHandle.WaitOneなどが含まれますThread.Join。したがって、それらをどこに配置するかについては賢明でなければなりません。ただし、ほとんどの場合、アルゴリズムによってそれらの場所が決定され、特にアルゴリズムがこれらのブロッキング呼び出しの 1 つでほとんどの時間を費やしている場合は、通常それで問題ありません。アルゴリズムが BCL のブロッキング呼び出しの 1 つを使用しない場合は、この方法は機能しません。ここでの理論は、 はThreadInterruptException.NET 待機呼び出しからのみ生成されるため、おそらく安全なポイントで。少なくとも、スレッドがアンマネージ コード内にあることや、クリティカル セクションから抜け出して、取得された状態のぶら下がりロックを残すことはできないことがわかります。これはそれほど侵入的ではありませんが、Thread.Abortどの呼び出しがこれに応答するかが明確ではなく、多くの開発者がそのニュアンスに馴染みがないため、使用はお勧めしません。

おすすめ記事