Распространение исключений в java.util.concurrent.CompletableFuture - PullRequest
0 голосов
/ 26 февраля 2019

Есть два фрагмента кода.

В первом мы создаем CompletableFuture из задачи, которая всегда выдает какое-то исключение.Затем мы применяем метод «исключительно» к этому будущему, а затем метод «Принять».Мы НЕ назначаем новое будущее, возвращаемое методом Accept, любой переменной.Затем мы призываем «присоединиться» к первоначальному будущему.То, что мы видим, это то, что «исключительно» метод был вызван так же, как и «thenAccept».Мы видим это, потому что они напечатали соответствующие строки в выводе.Но исключение не было подавлено «исключительно» методом.Подавить исключение и предоставить нам какое-то значение по умолчанию, вместо этого именно то, что мы ожидали от «исключительно» в этом случае.

Во втором фрагменте мы делаем почти то же самое, но назначаем новое возвращенное будущее переменной и вызываем «join» вЭто.В этом случае ожидаемое исключение подавляется.

С моей точки зрения, для первой части непротиворечивое поведение заключается либо в том, чтобы не подавлять исключение и не вызывать «исключение» и «thenAccept», либо вызывать исключение и подавлять исключение.,

Почему между нами что-то среднее?

Первый фрагмент:

public class TestClass {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(TestClass::doSomethingForInteger);

        future.exceptionally(e -> {
                    System.out.println("Exceptionally");
                    return 42;
                })
                .thenAccept(r -> {
                    System.out.println("Accept");
                });

        future.join();
    }

    private static int doSomethingForInteger() {
        throw new IllegalArgumentException("Error");
    }
}

Второй фрагмент:

public class TestClass {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(TestClass::doSomethingForInteger);

        CompletableFuture<Void> voidCompletableFuture = future.exceptionally(e -> {
            System.out.println("Exceptionally");
            return 42;
        })
                .thenAccept(r -> {
                    System.out.println("Accept");
                });

        voidCompletableFuture.join();
    }

    private static int doSomethingForInteger() {
        throw new IllegalArgumentException("Error");
    }
}

1 Ответ

0 голосов
/ 28 февраля 2019

Не существует такой вещи, как «подавление исключения».Когда вы вызываете exceptionally, вы создаете новое будущее, которое будет завершено с результатом предыдущего этапа или результатом оценки функции, если предыдущий этап был выполнен исключительно.Предыдущий этап, т. Е. Будущее, на которое вы вызываете exceptionally, не затрагивается.

Это относится к всем методам, связывающим зависимую функцию или действие.Каждый из этих методов создает новое будущее, которое будет завершено в соответствии с документацией.Ни один из них не влияет на существующее будущее, для которого вы вызываете метод.

Возможно, это станет намного понятнее в следующем примере:

CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    return "a string";
});

CompletableFuture<Integer> f2 = f1.thenApply(s -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
    return s.length();
});

f2.thenAccept(i -> System.out.println("result of f2 = "+i));

String s = f1.join();
System.out.println("result of f1 = "+s);

ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);

Здесь должно быть ясно, что результатиз зависимой стадии, Integer, не может заменить результат предварительной стадии, String.Это просто два разных фьючерса с разными результатами.И поскольку вызов join() для f1 запросов на результат первого этапа, он не зависит от f2 и, следовательно, даже не ожидает его завершения.(Это также причина, по которой код ожидает окончания всех фоновых операций в конце).

Использование exceptionally не отличается.Может показаться странным, что следующий этап имеет такой же тип и даже тот же результат в неисключительном случае, но это не меняет того факта, что существует два различных этапа.

static void report(String s, CompletableFuture<?> f) {
    f.whenComplete((i,t) -> {
        if(t != null) System.out.println(s+" completed exceptionally with "+t);
        else System.out.println(s+" completed with value "+i);
    });
}
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    throw new IllegalArgumentException("Error for testing");
});
CompletableFuture<Integer> f2 = f1.exceptionally(t -> 42);

report("f1", f1);
report("f2", f2);

ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);

Похоже, широко распространено мышление о CompletableFuture методах цепочки, которые являются своего рода конструктором для единого будущего, что, к сожалению, ошибочно.Еще одной ошибкой является следующая ошибка:

CompletableFuture<?> f = CompletableFuture.supplyAsync(() -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    System.out.println("initial stage");
    return "";
}).thenApply(s -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    System.out.println("second stage");
    return s;
}).thenApply(s -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    System.out.println("third stage");
    return s;
}).thenAccept(s -> {
    System.out.println("last stage");
});

f.cancel(true);
report("f", f);

ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);

Как объяснено, каждый цепочечный метод создает новую стадию, поэтому для получения приемлемой ссылки на стадию, возвращаемую последним цепочечным методом, т.е.конечный результат.Но отмена этого этапа приведет только к отмене этого последнего этапа и ни одного из обязательных этапов.Кроме того, после отмены последний этап больше не зависит от других этапов, поскольку он уже завершен путем отмены и может сообщить об этом исключительном результате, в то время как другие, теперь не связанные этапы, все еще оцениваются в фоновом режиме.

...