处理Ruby线程中引发的异常

时间:2012-02-01 11:52:49

标签: ruby multithreading exception-handling

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

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秒后捕获异常?如何通过任何线程捕获第一个引发的异常?

4 个答案:

答案 0 :(得分:57)

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

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

答案 1 :(得分:6)

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

答案 2 :(得分:0)

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

Set objCollection = IE.Document.getElementsByClassName("Class")
MsgBox objCollection.Length

答案 3 :(得分:0)

This will wait for the first thread to either raise or return (and re-raise):

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