Techioz Blog

Ruby スレッドで発生した例外の処理

概要

例外処理の古典的な問題の解決策を探しています。次のコード部分を考えてみましょう。

def foo(n)
  puts " for #{n}"
  sleep n
  raise "after #{n}"
end

begin
  threads = []
  [5, 15, 20, 3].each do |i|
    threads << Thread.new do
      foo(i)
    end
  end

  threads.each(&:join)      
rescue Exception => e
  puts "EXCEPTION: #{e.inspect}"
  puts "MESSAGE: #{e.message}"
end

このコードは 5 秒後に例外をキャッチします。

しかし、配列を [15, 5, 20, 3] に変更すると、上記のコードは 15 秒後に例外をキャッチします。つまり、最初のスレッドで発生した例外を常にキャッチします。

どのようなアイデアでも、なぜそうなるのか。毎回 3 秒後に例外をキャッチしないのはなぜですか?スレッドによって最初に発生した例外をキャッチするにはどうすればよいですか?

解決策

スレッド内の未処理の例外によってインタープリターが終了するようにするには、Thread::abort_on_Exception= を true に設定する必要があります。未処理の例外により、スレッドの実行が停止します。この変数を true に設定しない場合、スレッドの Thread#join または Thread#value を呼び出した場合にのみ例外が発生します。 true に設定すると、発生時に呼び出され、メインスレッドに伝播されます。

Thread.abort_on_exception=true # add this

def foo(n)
    puts " for #{n}"
    sleep n
    raise "after #{n}"
end

begin
    threads = []
    [15, 5, 20, 3].each do |i|
        threads << Thread.new do
            foo(i)
        end
    end
    threads.each(&:join)

rescue Exception => e

    puts "EXCEPTION: #{e.inspect}"
    puts "MESSAGE: #{e.message}"
end

出力:

 for 5
 for 20
 for 3
 for 15
EXCEPTION: #<RuntimeError: after 3>
MESSAGE: after 3

注: ただし、特定のスレッド インスタンスでこの方法で例外を発生させたい場合は、同様の abort_on_Exception= Thread インスタンス メソッドがあります。

t = Thread.new {
   # do something and raise exception
}
t.abort_on_exception = true

# Alternatively use Thread.current.abort_on_exception= 
# as first line within thread to ensure it is set before
# any other work is done by the thread.
t = Thread.new {
   Thread.current.abort_on_exception = true
   # do something and raise exception
}