使用以下脚本
threads = [
Thread.new { Thread.current.abort_on_exception = true; raise 'err' },
Thread.new { Thread.current.abort_on_exception = true; raise 'err' },
]
begin
threads.each(&:join)
rescue RuntimeError
puts "Got Error"
end
有一半时间我从出口0得到预期的“得到的错误”而另一半得到test.rb:3:in block in <main>': err (RuntimeError)
。
不应该救援能够处理这个吗?如果不是两个线程同时引发错误的替代解决方案可能是什么?
我考虑过不使用abort_on_exception = true
,但问题是如果第一个帖子在sleep(10)
之前有raise
,那么第二个线程会立即出错,不会被抓住直到10秒钟(由于threads
数组的顺序)。
Ruby MRI版本: ruby 2.4.0p0(2016-12-24修订版57164)[x86_64-darwin15]
任何想法都将不胜感激。谢谢!
更新
jruby-9.1.6.0似乎没有这个问题。可能是因为它固有的线程安全性。它始终打印Got Error
,没有任何例外。不幸的是,JRuby不适合我们。
答案 0 :(得分:1)
这里有几个难题。
首先,程序只等待主线程完成:
Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no!' }
puts 'Ready or not, here I come'
以上可能会或可能不会引发错误。
其次,如果你加入一个线程,那个线程引发的异常将由#join
方法的连接线程重新引发:
gollum = Thread.new { raise 'My precious!!!' }
begin
gollum.join
rescue => e
# Prints 'My precious!!!'
puts e.message
end
现在,执行将返回到已加入的线程。它不再加入导致错误的线程或任何其他线程。它没有加入其他线程的原因是因为你当时只能加入一个线程。 threads.each(&:join)
实际上将你加入到第一个,当它结束时 - 到第二个,依此类推:
frodo = Thread.new { raise 'Oh, no, Frodo!' }
sam = Thread.new { raise 'Oh, no, Sam!' }
begin
[frodo, sam].each(&:join)
rescue => e
puts e.message
end
puts 'This is the end, my only friend, the end.'
以上打印
哦,不,佛罗多! 这是结束,我唯一的朋友,结束。
现在让我们把它放在一起:
frodo = Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no, Frodo!' }
sam = Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no, Sam!' }
begin
[frodo, sam].each(&:join)
rescue => e
puts e.message
end
puts 'This is the end, my only friend, the end.'
这里可能发生很多事情。重要的是,如果我们设法加入(在此之前我们没有得到错误),救援将从主线程中捕获异常,从任何线程设法先提升它然后在救援后继续。之后,主线程(以及程序)可能会或可能不会在另一个线程引发其异常之前完成。
让我们检查一些可能的输出:
ex.rb:1:在'阻止'中:哦,不,佛罗多! (RuntimeError)
Frodo在我们加入之前提出了他的例外。
哦,不,山姆! 这是结束,我唯一的朋友,结束。
我们加入后,Sam是第一个提出错误的人。在主线程中打印出错误消息后,我们也打印了结束。然后主线程完成,然后Frodo可以提出他的错误。
哦,不,佛罗多!ex.rb:2:在'阻止'中:哦,不,山姆! (RuntimeError)
我们成功加入。佛罗多是第一个筹集,我们获救和印刷。 Sam在我们打印结束之前就已经成长了。
哦,不,山姆! 这是结束,我唯一的朋友,end.ex.rb:1:在'block in'中:哦,不,佛罗多! (RuntimeError)
(很少)我们设法得救了。 Sam首先提出错误,然后从主线程中打印出来。我们打印了结束。在打印之后,但在主线程终止之前,Frodo也成功地抓住了他的错误。
至于可能的解决方案,你只需要和可能引起的线程一样多的救援。请注意,我还将线程创建放在受保护的块中,以确保在连接之前捕获潜在的错误:
def execute_safely_concurrently(number_of_threads, &work)
return if number_of_threads.zero?
begin
Thread.new(&work).join
rescue => e
puts e
end
execute_safely_concurrently(number_of_threads.pred, &work)
end
execute_safely_concurrently(2) do
Thread.current.abort_on_exception = true
raise 'Handle me, bitte!'
end
答案 1 :(得分:0)
看了@ndn关于将每个线程包装在自己的救援中的想法。看起来这样可以解决问题。这是他的修改示例,它不会阻止在连接时执行。
@threads = []
def execute_safely_concurrently(&work)
begin
@threads << Thread.new(&work)
rescue RuntimeError => e
puts "Child Thread Rescue: #{e}"
end
end
execute_safely_concurrently do
Thread.current.abort_on_exception = true
sleep(3)
raise 'Handle me, bitte 1!'
end
execute_safely_concurrently do
Thread.current.abort_on_exception = true
raise 'Handle me, bitte 2!'
end
begin
@threads.each(&:join)
rescue RuntimeError => e
puts "Main Thread Rescue: #{e}"
end