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
}