如果线程抛出异常,我怎么能等到没有抛出异常的所有线程都完成(所以用户在一切都停止之前不会再次启动)?
我以几种不同的方式使用GPars,因此我需要一个策略(并行集合,异步闭包和fork / join)。例外情况不会被埋没,它们通过promises,getChildrenResults等很好地处理,所以这不是问题(感谢Vaclav Pech的答案)。我只需要确保主线程等待,直到任何仍在运行的完成或以其他方式停止。
例如,当使用并行集合时,一些线程继续运行,而一些线程在异常之后永远不会启动。因此,要告诉我们有多少人可以等待,或者可能暂时抓住他们,这并不容易。
我猜可能有一种方法可以使用线程池(在本例中为GParsPool)。有什么建议吗?
谢谢!
答案 0 :(得分:3)
我相信我有一个问题的解决方案,我在完成测试后在应用程序中实现了它并且它可以工作。
withPool闭包传入创建的池(jsr166y.ForkJoinPool)作为第一个参数。我可以抓住它并将其存储在一个变量( currentPool )中,稍后由主线程使用,如下所示:
GParsPool.withPool { pool ->
currentPool = pool
当抛出异常并返回主线程进行处理时,我可以让它等到所有内容都完成,如下所示:
} catch (Exception exc) {
if (currentPool) {
while (!currentPool.isQuiescent()) {
Thread.sleep(100)
println 'waiting for threads to finish'
}
}
println 'all done'
}
isQuiescent()似乎是一种安全的方法,可以确保没有更多的工作要做。
请注意,在测试期间,我还发现异常似乎并没有像我原先想的那样终止循环的执行。如果我有一个500的列表并且执行了eachParallel,那么无论第一个通过是否有错误,它们都会运行。所以我不得不在并行循环的异常处理程序中使用currentPool.shutdownNow()来终止循环。另见:GPars - proper way to terminate a parallel collection early
以下是实际解决方案的完整简化表示:
void example() {
jsr166y.ForkJoinPool currentPool
AtomicInteger threadCounter = new AtomicInteger(0)
AtomicInteger threadCounterEnd = new AtomicInteger(0)
AtomicReference<Exception> realException = new AtomicReference<Exception>()
try {
GParsPool.withPool { pool ->
currentPool = pool
(1..500).eachParallel {
try {
if (threadCounter.incrementAndGet() == 1) {
throw new RuntimeException('planet blew up!')
}
if (realException.get() != null) {
// We had an exception already in this eachParallel - quit early
return
}
// Do some long work
Integer counter=0
(1..1000000).each() {counter++}
// Flag if we went all the way through
threadCounterEnd.incrementAndGet()
} catch (Exception exc) {
realException.compareAndSet(null, exc)
pool.shutdownNow()
throw realException
}
}
}
} catch (Exception exc) {
// If we used pool.shutdownNow(), we need to look at the real exception.
// This is needed because pool.shutdownNow() sometimes generates a CancellationException
// which can cover up the real exception that caused us to do a shutdownNow().
if (realException.get()) {
exc = realException.get()
}
if (currentPool) {
while (!currentPool.isQuiescent()) {
Thread.sleep(100)
println 'waiting for threads to finish'
}
}
// Do further exception handling here...
exc.printStackTrace()
}
}
回到我之前的例子,如果我第一次在4核机器上抛出异常,那么大约有5个线程排队。 shutdownNow()会在大约20个左右的线程通过后切掉一些东西,所以在顶部附近进行“早退”检查可以帮助那些20左右的线程尽快退出。
只是将它发布在这里,以防它帮助其他人,以换取我所得到的所有帮助。谢谢!
答案 1 :(得分:2)
我相信您需要捕获异常,然后返回除预期结果之外的其他内容(例如String或null,如果您期望某个数字),即;
@Grab('org.codehaus.gpars:gpars:0.12')
import static groovyx.gpars.GParsPool.*
def results = withPool {
[1,2,3].collectParallel {
try {
if( it % 2 == 0 ) {
throw new RuntimeException( '2 fails' )
}
else {
Thread.sleep( 2000 )
it
}
}
catch( e ) { e.class.name }
}
}