处理 Ruby 线程中引发的异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9095316/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 04:44:08  来源:igfitidea点击:

Handling exceptions raised in a Ruby thread

rubymultithreadingexception-handling

提问by Akash Agrawal

I am looking for a solution of classic problem of exception handling. Consider following piece of code:

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

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

This code catches the exception after 5 seconds.

此代码在 5 秒后捕获异常。

But if I change the array as [15, 5, 20, 3], above code catch the exception after 15 seconds. In short, it always catch the exception raised in first thread.

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

Any idea, why so. Why doesn't it catch the exception after 3 seconds each time? How do I catch the first raised exception by any thread?

任何想法,为什么会这样。为什么每次 3 秒后不捕获异常?如何捕获任何线程第一个引发的异常?

回答by Aliaksei Kliuchnikau

If you want any unhandled exception in any thread to cause the interpreter to exit, you need to set Thread::abort_on_exception=to true. Unhandled exception cause the thread to stop running. If you don't set this variable to true, exception will only be raised when you call Thread#joinor Thread#valuefor the thread. If set to true it will be raised when it occurs and will propagate to the main thread.

如果您希望任何线程中任何未处理的异常导致解释器退出,则需要将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

Output:

输出:

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

Note: but if you want any particular thread instance to raise exception this way there are similar abort_on_exception= Thread instance method:

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

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

回答by Jason Ling

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

回答by Daniel Garmoshka

Postponed exceptions processing (Inspired by @Jason Ling)

延迟的异常处理(灵感来自@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'

回答by grosser

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