处理Ruby线程中引发的exception

我正在寻找一个经典的exception处理问题的解决方案。 考虑以下代码:

def foo(n) puts " for #{n}" sleep n raise "after #{n}" end begin threads = [] [5, 15, 20, 3].each do |i| threads < e puts "EXCEPTION: #{e.inspect}" puts "MESSAGE: #{e.message}" end 

此代码在5秒后捕获exception。

但是如果我将数组更改为[15, 5, 20, 3] ,则上面的代码会在15秒后捕获exception。 简而言之,它始终捕获第一个线程中引发的exception。

任何想法,为什么如此。 为什么不在每次3秒后捕获exception? 如何通过任何线程捕获第一个引发的exception?

如果您希望任何线程中的任何未处理的exception导致解释器退出,则需要将Thread :: abort_on_exception =设置为true 。 未处理的exception导致线程停止运行。 如果未将此变量设置为true,则仅在为线程调用Thread#joinThread#value时才会引发exception。 如果设置为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: # MESSAGE: after 3 

注意:但如果您希望任何特定的线程实例以这种方式引发exception,则有类似的abort_on_exception = Thread实例方法 :

 t = Thread.new { # do something and raise exception } t.abort_on_exception = true 
 Thread.class_eval do alias_method :initialize_without_exception_bubbling, :initialize def initialize(*args, &block) initialize_without_exception_bubbling(*args) { begin block.call rescue Exception => e Thread.main.raise e end } end end 

推迟exception处理(灵感来自@Jason Ling)

 class SafeThread < Thread def initialize(*args, &block) super(*args) do begin block.call rescue Exception => e @exception = e end end end def join raise_postponed_exception super raise_postponed_exception end def raise_postponed_exception Thread.current.raise @exception if @exception end end puts :start begin thread = SafeThread.new do raise 'error from sub-thread' end puts 'do something heavy before joining other thread' sleep 1 thread.join rescue Exception => e puts "Caught: #{e}" end puts 'proper end' 

这将等待第一个线程加注或返回(并重新加注):

 require 'thwait' def wait_for_first_block_to_complete(*blocks) threads = blocks.map do |block| Thread.new do block.call rescue StandardError $! end end waiter = ThreadsWait.new(*threads) value = waiter.next_wait.value threads.each(&:kill) raise value if value.is_a?(StandardError) value end