it-swarm.com.de

Behandlung von Ausnahmen in einem Ruby-Thread

Ich suche nach einer Lösung des klassischen Problems der Ausnahmebehandlung. Betrachten Sie den folgenden 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

Dieser Code fängt die Ausnahme nach 5 Sekunden ab.

Wenn ich das Array jedoch als [15, 5, 20, 3] ändere, fängt der obige Code nach 15 Sekunden die Ausnahme ein. Kurz gesagt, fängt es immer die Ausnahme, die im ersten Thread ausgelöst wurde.

Jede Idee, warum das so ist. Warum wird die Ausnahme nicht jedes Mal nach 3 Sekunden erkannt? Wie fange ich die erste hervorgehobene Ausnahme durch einen Thread?

28
Akash Agrawal

Wenn Sie möchten, dass eine nicht behandelte Ausnahme in einem Thread dazu führt, dass der Interpreter beendet wird, müssen Sie Thread :: abort_on_exception = auf true setzen. Eine nicht behandelte Ausnahme führt dazu, dass der Thread nicht mehr ausgeführt wird. Wenn Sie diese Variable nicht auf true setzen, wird die Ausnahme nur ausgelöst, wenn Sie Thread#join oder Thread#value für den Thread aufrufen. Wenn der Wert auf true gesetzt ist, wird er ausgelöst, wenn er auftritt, und wird an den Haupt-Thread weitergeleitet.

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

Ausgabe:

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

Hinweis: Wenn jedoch eine bestimmte Thread-Instanz eine Ausnahme auf diese Weise auslösen soll, gibt es eine ähnliche abort_on_exception = Thread-Instanzmethode :

t = Thread.new {
   # do something and raise exception
}
t.abort_on_exception = true
54
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
6
Jason Ling

Bearbeitung von Ausnahmen verschoben (Inspiriert von @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
Dan Key

Dies wird warten, bis der erste Thread entweder angehoben oder zurückgegeben (und erneut angehoben) wird:

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
0
grosser