Обработка исключений, возникающих в потоке Ruby - PullRequest
32 голосов
/ 01 февраля 2012

Я ищу решение классической проблемы обработки исключений. Рассмотрим следующий фрагмент кода:

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 ]

57 голосов
/ 01 февраля 2012

Если вы хотите, чтобы любое необработанное исключение в каком-либо потоке вызывало выход интерпретатора, вам нужно установить 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 = метод экземпляра потока :

t = Thread.new {
   # do something and raise exception
}
t.abort_on_exception = true
5 голосов
/ 30 апреля 2012
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
1 голос
/ 08 мая 2018

Отложенная обработка исключений (вдохновлено @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'
0 голосов
/ 18 сентября 2018

Это будет ждать, пока первый поток либо поднимется, либо вернется (и повторно поднимется):

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...