【问题标题】:Handling exceptions raised in a Ruby thread处理 Ruby 线程中引发的异常
【发布时间】:2012-02-24 02:28:39
【问题描述】:

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

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 秒后都没有捕捉到异常?如何捕获任何线程引发的第一个异常?

【问题讨论】:

    标签: ruby multithreading exception-handling


    【解决方案1】:

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

    【讨论】:

    • 感谢您的回答。我知道 abort_on_exception 标志。但我的要求是知道哪个是第一个引发异常的线程,然后对其做出一些决定。
    • @AkashAgrawal,我没有得到你最后的评论。您将在rescue 子句中捕获第一个异常(来自具有睡眠 3 的线程),在这里您可以做出决定。如果您的主线程在第一个异常后没有退出,则所有其余线程将继续运行。
    • 所以这是一个测试代码。我的问题是如何捕获从任何线程抛出的第一个异常。任何线程都可以随时抛出异常。
    • Thread.abort_on_exception = true 是一个全局设置,因此请注意它可能会破坏应用程序中的其他代码,或者它的依赖项需要默认行为。我会选择t.abort_on_exception = true 方法,除非它是一个非常小的脚本。
    • 如果此代码位于 gem 中,或者将被您无法控制的代码调用,那么实例方法也不是很有帮助,因为您实际上是在将中止行为指定给整个运行时,大多数情况下您不知道或(应该)控制的。
    【解决方案2】:
    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
    

    【讨论】:

    • 投了反对票,因为这只是没有解释的代码。需要 cmets,描述它的作用、原因等。
    • 也不要覆盖 ruby​​ 核心类。 -1'd
    • 我不同意这两个 cmets。
    • Thread.main.raise e 可以很好地将异常向上传播到堆栈。
    【解决方案3】:

    延迟的异常处理(受@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'
    

    【讨论】:

      【解决方案4】:

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

      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
      

      【讨论】:

        【解决方案5】:

        Jason Ling's answer 将丢失任何传递给 Thread.new 的参数。这将打破 Puma 和其他宝石。为避免此问题,您可以使用:

        Thread.class_eval do
          alias_method :initialize_without_exception_bubbling, :initialize
          def initialize(*args, &block)
            initialize_without_exception_bubbling(*args) {
              begin
                block.call(*args)
              rescue Exception => e
                Thread.main.raise e
              end
            }
          end
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-01-17
          • 2010-12-23
          • 1970-01-01
          • 2014-06-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多