当其中之一抛出异常时如何防止执行

时间:2019-09-10 17:33:49

标签: java asynchronous completable-future

我有一组元素,对于每个元素,我正在执行方法,方法是将其作为Runnable传递给CompletableFuture.runAsync()。在执行过程中,可能需要停止整个计算,因此我在执行方法之前检查了一些条件。如果应该停止计算,则抛出异常,该异常在CompletableFuture之外处理。我想防止执行所有Runnable,它们在引发异常后执行。因此,换句话说,当它们中的任何一个抛出异常时,我都不想等待所有的CompletableFuture完成。

Set elements = ...
Executor executor = Executors.newFixedThreadPool(N);
try {
    CompletableFuture.allOf(elements.stream().map(e - > CompletableFuture.runAsync(() - > {
        if (shouldStop()) {
            throw new MyException();
        }
        myMethod(e);
    }, executor)).toArray(CompletableFuture[]::new)).join()
} catch (CompletionException e) {
    ...
}

3 个答案:

答案 0 :(得分:1)

仅在发生异常时取消所有它们。障碍是您在创建它们时并不了解所有这些知识,并且不想一次以上地完成这项工作。这可以通过首先创建一个新的空CompletableFuture(我们将其命名为f1)来解决。然后,像以前一样创建期货,但是在f1.cancel语句中插入对if(shouldStop()) { … }的调用。然后,在创建所有期货之后,将一个操作将所有取消的操作链接到f1期货上。

取消操作有两个目的,它将阻止执行尚未开始的可运行对象,并使allOf返回的将来不再等待仍在进行的评估完成。

由于取消CompletableFuture与用CancellationException例外完成没有区别,并且在有多个异常的情况下,allOf返回的future将报告任意一个,我们可以使用completeExceptionally改用自定义MyException,以确保报告的异常不会是次要的CancellationException

一个独立的例子是:

static final AtomicInteger STOP = new AtomicInteger(2);
static boolean shouldStop() {
    return STOP.getAndDecrement() <= 0;
}
static final int N = 10;
public static void main(String[] args) {
    Set<Integer> elements = IntStream.range(0, 100).boxed().collect(Collectors.toSet());
    ExecutorService executor = Executors.newFixedThreadPool(N);
    try {
        CompletableFuture<?> cancelAll = new CompletableFuture<>();
        CompletableFuture<?>[] all = elements.stream()
            .map(e ->
                CompletableFuture.runAsync(() -> {
                    System.out.println("entered "+e);
                    if(shouldStop()) {
                        RuntimeException myException = new RuntimeException("stopped");
                         // alternatively cancelAll.cancel(false);
                        cancelAll.completeExceptionally(myException);
                        throw myException;
                    }
                    System.out.println("processing "+e);
                }, executor))
            .toArray(CompletableFuture<?>[]::new);
        cancelAll.whenComplete((value,throwable) -> {
            if(throwable != null) {
                for(CompletableFuture<?> cf: all) cf.completeExceptionally(throwable);
            }
        });
        CompletableFuture.allOf(all).join();
    } catch (CompletionException e) {
        e.printStackTrace();
    }
    executor.shutdown();
}

将打印类似

的内容
entered 3
entered 8
entered 4
entered 6
entered 1
entered 9
entered 0
entered 7
entered 5
entered 2
entered 10
processing 8
processing 3
java.util.concurrent.CompletionException: java.lang.RuntimeException: stopped
    at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:331)
    at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:346)
    at java.base/java.util.concurrent.CompletableFuture$BiRelay.tryFire(CompletableFuture.java:1423)
    at java.base/java.util.concurrent.CompletableFuture$CoCompletion.tryFire(CompletableFuture.java:1144)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2088)
    at CompletableFutureTest.lambda$main$3(CompletableFutureTest.java:34)
    at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
    at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2088)
    at CompletableFutureTest.lambda$main$0(CompletableFutureTest.java:26)
    at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: stopped
    at CompletableFutureTest.lambda$main$0(CompletableFutureTest.java:25)
    ... 4 more

表明由于并发性,一些可运行对象已经在运行,但是一旦传播取消,就不会开始后续执行。

请注意,由于cancelAll只会以特殊的方式或根本不会完成,因此您可以将链接的操作简化为cancelAll.whenComplete((value,throwable) -> { for(CompletableFuture<?> cf: all) cf.completeExceptionally(throwable); });,但是是否保留冗余检查只是编码风格的问题

您还可以在处理步骤中添加一个延迟,以确保如果满足停止条件,allOf(all).join()就不会等待完成。

还可以将一个操作与runAsync返回的期货关联起来,这将在任何异常完成时取消所有交易,而不仅仅是明确的止损。但是,然后必须注意返回代表通过runAsync计划的操作的原始未来,而不是返回whenComplete返回的未来。

CompletableFuture<?> cancelAll = new CompletableFuture<>();
CompletableFuture<?>[] all = elements.stream()
    .map(e -> {
        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
            System.out.println("entered "+e);
            if(shouldStop()) throw new RuntimeException("stopped");
            System.out.println("processing "+e);
        }, executor);
        cf.whenComplete((value,throwable) -> {
            if(throwable != null) cancelAll.completeExceptionally(throwable);
        });
        return cf;
    })
    .toArray(CompletableFuture<?>[]::new);
cancelAll.whenComplete((value,throwable) -> {
    for(CompletableFuture<?> cf: all) cf.completeExceptionally(throwable);
});
CompletableFuture.allOf(all).join();

答案 1 :(得分:0)

我对CompletableFuture的使用经验不多,但确实有建议(可能有帮助吗?) 您可以在try块之外的CompletableFuture.allOf(elements.stream().map中声明lambda吗?这样,所有的期货都无法运行,直到进入尝试为止。但是它们仍然可以被catch块访问。然后,您可以cancel全部使用它们。

答案 2 :(得分:0)

您应该做的主要事情是interrupt您想要更快终止的所有正在运行的任务,这意味着这些任务可能需要检查中断,以便他们知道停止正在执行的操作并更快地终止。

另外,您可以在主线程中继续执行并让它们在后台终止,而不必等待被中断的任务真正终止。

public static void main(String[] args) {
    List<Integer> elements = Arrays.asList(5, null, 6, 3, 4); // these elements will fail fast
    // List<Integer> elements = Arrays.asList(5, 2, 6, 3, 4); // these elements will succeed

    try {
        CountDownLatch latch = new CountDownLatch(elements.size());
        ExecutorService executor = Executors.newFixedThreadPool(elements.size());
        elements.stream().forEach(e -> {
            executor.execute(() -> {
                try {
                    doSomething(e);
                    latch.countDown();
                } catch (Exception ex) {
                    // shutdown executor ASAP on exception, read the docs for `shutdownNow()`
                    // it will interrupt all tasks in the executor
                    if (!executor.isShutdown()) {
                        executor.shutdownNow();
                    }
                    for (int i = (int) latch.getCount(); i >= 0; i--) {
                        latch.countDown();
                    }
                    // log the exception
                    ex.printStackTrace(System.out);
                }
            });
        });
        latch.await();
        if (executor.isShutdown()) {
            System.out.println("Tasks failed! Terminating remaining tasks in the background.");
        } else {
            executor.shutdown();
            System.out.println("Tasks succeeded!");
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public static void doSomething(Integer sleepSecs) {
    // You will want to check for `interrupted()` throughout the method you want to be able to cancel
    if (Thread.interrupted()) {
        System.out.println(Thread.currentThread().getName() + " interrupted early");
        return;
    }

    if (sleepSecs == null) {
        System.out.println(Thread.currentThread().getName() + " throwing exception ");
        throw new RuntimeException();
    }

    try {
        System.out.println(Thread.currentThread().getName() + " started interruptable sleep for " + sleepSecs + "s");
        Thread.sleep(sleepSecs * 1000);
        System.out.println(Thread.currentThread().getName() + " finished interruptable sleep" + sleepSecs + "s");
    } catch (InterruptedException e) {
        System.out.println(Thread.currentThread().getName() + " interrupted sleep!");
    }

    // ...possibly some part of the task that can't be skipped, such as cleanup

    System.out.println(Thread.currentThread().getName() + " complete!");
}
相关问题