Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonの子スレッドで発生した例外を呼び出し元スレッドでキャッチする方法

なぜそのままでは例外を捕捉できないのか

問題の本質は、thread_obj.start() が即座に制御を返してしまう点にあります。開始された子スレッドは、独自のコンテキスト・独自のスタック上で実行されるため、そこで発生した例外はすべて子スレッドのコンテキスト内に閉じ込められます。

つまり、子スレッド内で例外が起きても、それだけでは呼び出し元(メイン)スレッドには何も伝わりません。例外の情報を親スレッドへ届けるには、何らかのメッセージとして明示的に受け渡しを行う必要があります。

解決策:Queue を使って例外情報を親スレッドへ渡す

最もシンプルで定番的な手法は、queue.Queue を例外情報の受け渡し口として使うことです。以下のようにコードを書き換えられます。

import sys
import threading
import Queue

class ExcThread(threading.Thread):
    def __init__(self, foo):
        threading.Thread.__init__(self)
        self.foo = foo

    def run(self):
        try:
            raise Exception('An error occurred here.')
        except Exception:
            # 発生した例外の情報をキューに格納して親へ通知
            self.foo.put(sys.exc_info())

def main():
    foo = Queue.Queue()
    thread_obj = ExcThread(foo)
    thread_obj.start()

    while True:
        try:
            # ノンブロッキングでキューから例外情報を取り出す
            exc = foo.get(block=False)
        except Queue.Empty:
            pass
        else:
            exc_type, exc_obj, exc_trace = exc
            print exc_type, exc_obj
            print exc_trace

        thread_obj.join(0.1)
        if thread_obj.isAlive():
            continue

コードの動作ポイント

  • ExcThread クラスrun() 内で try / except を使い、例外を握りつぶさずに sys.exc_info() で「例外型・例外オブジェクト・トレースバック」の3要素を取得します。
  • キューへの投入: 取得した例外情報を self.foo.put() で共有キューに入れます。これが親スレッドへの「メッセージ」になります。
  • メインスレッド側の監視: メインスレッドは foo.get(block=False) でノンブロッキングにキューをチェックし、例外情報があれば内容を出力します。
  • 終了待ち合わせjoin(0.1) で短時間ずつスレッドの完了を待ち、まだ生存していれば (isAlive()) ループを継続します。

Python 3 での書き方の違い

上記のコードはPython 2系の記法です。Python 3で利用する場合は、次の点を読み替えてください。

  • import Queueimport queue、クラス名も queue.Queue に変更
  • except Queue.Emptyexcept queue.Empty
  • print 文print() 関数
  • isAlive()is_alive()(非推奨名の変更)

さらに実用的な場面では、キューを介したポーリングの代わりに concurrent.futures.ThreadPoolExecutor を使えば、future.result() の呼び出し時に子スレッドの例外がそのまま再送出されるため、より簡潔に同じ目的を実現できます。

  1. PythonでIOError例外をキャッチする方法【初心者向けにわかりやすく解説】

    IOError例外とは? IOError(入出力エラー)は、入出力操作が失敗したときに発生する例外です。代表的な例としては、存在しないファイルをopen()関数で開こうとした場合や、OSに関連するエラーが発生した場合などが挙げられます。 なお、Python 3ではIOErrorはOSErrorの別名(エイリアス)となっており、どちらの名前で捕捉しても同じ例外を受け取ることができます。 try-exceptブロックで例外を処理する ファイルを開く処理などをtryブロック内に記述すると、入出力例外が発生した際にexceptブロックで適切に処理できます。以下に具体的なコード例を示します。 コード例(

  2. PythonでKeyError例外をキャッチする方法をわかりやすく解説

    Pythonでは、辞書(dict)に存在しないキーを使って値へアクセスしようとすると、KeyError例外が発生します。この記事では、try-except文を使ってKeyErrorをキャッチし、例外の種類や詳細情報を確認する方法を解説します。 KeyErrorが発生する仕組み 次のコードは、辞書に存在しないキー c で値を取得しようとしているため、KeyErrorが発生します。sys.exc_info() を使うと、発生した例外の型などの情報を取得できます。 import sys try: s = {a: 5, b: 7}[c] except: print(sys.exc_i