これは何度も何度も議論されてきましたが、これを達成する最善の方法についてはまだよく理解していません.
メイン アプリ スレッドとワーカー スレッドの 2 つのスレッドがあるとします。メイン アプリ スレッド (WXWidgets GUI スレッド、またはループしてコンソールでユーザー入力を受け入れるスレッドなど) には、ワーカー スレッドを停止する理由がある可能性があります - ユーザーがアプリケーションを閉じた、停止ボタンがクリックされた、何らかのエラーが発生したメインスレッドで発生しました。
一般的に提案されているのは、スレッドが終了するかどうかを判断するために頻繁にチェックするフラグを設定することです。ただし、これにアプローチするための提案された方法には2つの問題があります。
まず、フラグの定期的なチェックをコードに書き込むと、コードが非常に見苦しくなります。また、大量のコードの重複により、問題が発生しやすくなります。次の例を見てください。
def WorkerThread():
while (True):
doOp1() # assume this takes say 100ms.
if (exitThread == True):
safelyEnd()
return
doOp2() # this one also takes some time, say 200ms
if (exitThread == True):
safelyEnd()
return
if (somethingIsTrue == True):
doSomethingImportant()
if (exitThread == True): return
doSomethingElse()
if (exitThread == True): return
doOp3() # this blocks for an indeterminate amount of time - say, it's waiting on a network respond
if (exitThread == True):
safelyEnd()
return
doOp4() # this is doing some math
if (exitThread == True):
safelyEnd()
return
doOp5() # This calls a buggy library that might block forever. We need a way to detect this and kill this thread if it's stuck for long enough...
saveSomethingToDisk() # might block while the disk spins up, or while a network share is accessed...whatever
if (exitThread == True):
safelyEnd()
return
def safelyEnd():
cleanupAnyUnfinishedBusiness() # do whatever is needed to get things to a workable state even if something was interrupted
writeWhatWeHaveToDisk() # it's OK to wait for this since it's so important
コードを追加したりコードを変更したりする場合は、それらのチェック ブロックをあちこちに追加していることを確認する必要があります。ワーカー スレッドが非常に長いスレッドである場合、数十または数百ものチェックを簡単に行うことができます。非常に面倒です。
他の問題を考えてみてください。doOp4() が誤ってデッドロックした場合、アプリは永遠にスピンし、終了しません。ユーザーエクスペリエンスが良くない!
safelyEnd()
デーモン スレッドを使用することも、コードを実行する機会を奪ってしまうため、あまり良い選択肢ではありません。このコードは、ディスク バッファのフラッシュ、デバッグ目的でのログ データの書き込みなど、重要な場合があります。
2 つ目は、頻繁にチェックする機会がない場所をブロックする関数をコードが呼び出す可能性があることです。この関数は存在しますが、アクセスできないコード内にあるとしましょう-ライブラリの一部としましょう:
def doOp4():
time.sleep(60) # imagine that this is a network thread, that waits for 60 seconds for a reply before returning.
そのタイムアウトが 60 秒の場合、たとえメイン スレッドがスレッドを終了するようにシグナルを送ったとしても、ネットワーク応答の待機を停止して終了することが完全に合理的な場合でも、メイン スレッドは 60 秒間そこに留まる可能性があります。ただし、そのコードが私が作成したものではないライブラリの一部である場合、それがどのように機能するかを制御することはできません。
ネットワーク チェック用のコードを書いたとしても、基本的にリファクタリングして、60 秒待つのではなく、60 回ループして 1 秒待ってから終了スレッドをチェックするようにする必要があります。繰り返しますが、非常に面倒です!
これらすべての結果として、これを簡単に実装できる良い方法は、特定のスレッドで例外を発生させることです。それができれば、ワーカー スレッドのコード全体を try ブロックでラップし、そのsafelyEnd()
コードを例外ハンドラーまたはブロックに入れることができfinally
ます。
これを達成する方法、または物事を機能させる別の手法でこのコードをリファクタリングする方法はありますか? 理想的には、ユーザーが終了を要求したときに、可能な限り最小限の時間を待たせたいということです。これはアプリでは非常に一般的なことなので、これを達成するための簡単な方法が必要なようです!
スレッド通信オブジェクトのほとんどは、このタイプのセットアップを許可していません。それらは終了フラグを持つためのよりクリーンな方法を可能にするかもしれませんが、それでもその終了フラグを常にチェックする必要がなくなるわけではなく、外部呼び出しまたは単に忙しいループ。
私にとって最大の問題は、長いワーカー スレッド プロシージャがある場合、何百ものフラグ チェックを散らかさなければならないことです。これはあまりにも面倒で、あまり良いコーディング方法とは思えません。もっと良い方法があるはずです...
アドバイスをいただければ幸いです。