如何知道GPars在抛出异常时所有线程都已完成?

时间:2012-12-07 21:36:16

标签: groovy gpars

如果线程抛出异常,我怎么能等到没有抛出异常的所有线程都完成(所以用户在一切都停止之前不会再次启动)?

我以几种不同的方式使用GPars,因此我需要一个策略(并行集合,异步闭包和fork / join)。例外情况不会被埋没,它们通过promises,getChildrenResults等很好地处理,所以这不是问题(感谢Vaclav Pech的答案)。我只需要确保主线程等待,直到任何仍在运行的完成或以其他方式停止。

例如,当使用并行集合时,一些线程继续运行,而一些线程在异常之后永远不会启动。因此,要告诉我们有多少人可以等待,或者可能暂时抓住他们,这并不容易。

我猜可能有一种方法可以使用线程池(在本例中为GParsPool)。有什么建议吗?

谢谢!

2 个答案:

答案 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 }
  }
}