Подтвердить что ты не робот

Завершенные заглавные ласточки?

Я играл с CompletableFuture и заметил странную вещь.

String url = "http://google.com";

CompletableFuture<String> contentsCF = readPageCF(url);
CompletableFuture<List<String>> linksCF = contentsCF.thenApply(_4_CompletableFutures::getLinks);

linksCF.thenAccept(list -> {
    assertThat(list, not(empty()));
});

linksCF.get();

Если в моем вызове thenAccept утверждение не выполняется, исключение не распространяется. Я попробовал что-то еще более уродливое:

linksCF.thenAccept(list -> {
    String a = null;
    System.out.println(a.toString());
});

ничего не происходит, никакое исключение не распространяется. Я попытался использовать методы, такие как handle и другие, связанные с исключениями в CompletableFutures, но не удалось - никому не передано исключение.

Когда я отлаживаю код CompletableFuture, он блокирует исключение, делает следующее:

final void internalComplete(T v, Throwable ex) {
    if (result == null)
        UNSAFE.compareAndSwapObject
            (this, RESULT, null,
             (ex == null) ? (v == null) ? NIL : v :
             new AltResult((ex instanceof CompletionException) ? ex :
                           new CompletionException(ex)));
    postComplete(); // help out even if not triggered
}

и ничего больше.

Я на JDK 1.8.0_05 x64, Windows 7.

Я что-то пропустил?

4b9b3361

Ответ 1

Проблема заключается в том, что вы никогда не запрашиваете результаты вашего вызова linksCF.thenAccept(..).

Ваш вызов linksCF.get() будет ждать результатов выполнения в вашей цепочке. Но это только вернет результаты тогдашнего linksCF будущего. Это не включает результаты вашего утверждения.

linksCF.thenAccept(..) вернет новый экземпляр CompletableFuture. Чтобы получить вызванный исключение вызов get() или проверить статус исключения с помощью isCompletedExceptionally() на вновь возвращаемом экземпляре CompletableFuture.

CompletableFuture<Void> acceptedCF = linksCF.thenAccept(list -> {
    assertThat(list, not(empty()));
});

acceptedCF.exceptionally(th -> {
    // will be executed when there is an exception.
    System.out.println(th);
    return null;
});
acceptedCF.get(); // will throw ExecutionException once results are available

Alternative?

CompletableFuture<List<String>> appliedCF = linksCF.thenApply(list -> {
    assertThat(list, not(empty()));
    return list;
});

appliedCF.exceptionally(th -> {
    // will be executed when there is an exception.
    System.out.println(th);
    return Coolections.emptyList();
});
appliedCF.get(); // will throw ExecutionException once results are available

Ответ 2

Хотя на этот вопрос в основном уже ответил Грегор Куккуллис (+1), вот MCVE, который я создал для проверки этого.

Существует несколько вариантов получения фактического исключения, вызвавшего внутреннюю проблему. Однако я не понимаю, почему вызов get в будущем, возвращаемый thenAccept, должен быть проблемой. Если вы сомневаетесь, вы также можете использовать thenApply с функцией идентификации и использовать хороший плавный шаблон, например, в

List<String> list = 
    readPage().
    thenApply(CompletableFutureTest::getLinks).
    thenApply(t -> {
        // check assertion here
        return t;
    }).get();

Но может быть, есть особая причина, по которой вы хотите избежать этого.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

public class CompletableFutureTest
{
    public static void main(String[] args) 
        throws InterruptedException, ExecutionException
    {
        CompletableFuture<String> contentsCF = readPage();
        CompletableFuture<List<String>> linksCF = 
            contentsCF.thenApply(CompletableFutureTest::getLinks);

        CompletableFuture<Void> completionStage = linksCF.thenAccept(list -> 
        {
            String a = null;
            System.out.println(a.toString());
        });        

        // This will NOT cause an exception to be thrown, because
        // the part that was passed to "thenAccept" will NOT be
        // evaluated (it will be executed, but the exception will
        // not show up)
        List<String> result = linksCF.get();
        System.out.println("Got "+result);


        // This will cause the exception to be thrown and
        // wrapped into an ExecutionException. The cause
        // of this ExecutionException can be obtained:
        try
        {
            completionStage.get();
        }
        catch (ExecutionException e)
        {
            System.out.println("Caught "+e);
            Throwable cause = e.getCause();
            System.out.println("cause: "+cause);
        }

        // Alternatively, the exception may be handled by
        // the future directly:
        completionStage.exceptionally(e -> 
        { 
            System.out.println("Future exceptionally finished: "+e);
            return null; 
        });

        try
        {
            completionStage.get();
        }
        catch (Throwable t)
        {
            System.out.println("Already handled by the future "+t);
        }

    }

    private static List<String> getLinks(String s)
    {
        System.out.println("Getting links...");
        List<String> links = new ArrayList<String>();
        for (int i=0; i<10; i++)
        {
            links.add("link"+i);
        }
        dummySleep(1000);
        return links;
    }

    private static CompletableFuture<String> readPage()
    {
        return CompletableFuture.supplyAsync(new Supplier<String>() 
        {
            @Override
            public String get() 
            {
                System.out.println("Getting page...");
                dummySleep(1000);
                return "page";
            }
        });
    }

    private static void dummySleep(int ms)
    {
        try
        {
            Thread.sleep(ms);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
}

Ответ 3

Если в моем вызове thenAccept утверждение не выполняется, исключение не распространяется.

Продолжение, которое вы регистрируете с помощью thenAccept(), является отдельной задачей из будущего linksCF. Задача linksCF выполнена успешно; для сообщения об этом нет ошибки. Он имеет свое окончательное значение. Исключение, созданное linksCF, должно указывать только на проблему, порождающую результат linksCF; если какой-либо другой фрагмент кода, который потребляет результат, не указывает на неспособность произвести результат.

Чтобы наблюдать исключение, которое происходит в продолжении, вы должны наблюдать CompletableFuture продолжения.

правильно. но 1) я не должен принуждаться к вызову get() - одной из точек новых конструкций; 2) он завершен в ExecutionException

Что делать, если вы хотите передать результат нескольким независимым продолжениям с помощью thenAccept()? Если одно из этих продолжений должно было бросить, почему это должно повлиять на родителя или на другие продолжения?

Если вы хотите рассматривать linksCF как node в цепочке и наблюдать результат (и любые исключения), которые происходят в цепочке, тогда вы должны называть get() на последнем звене в цепочке.

Вы можете избежать отмеченного ExecutionException, используя join() вместо get(), который будет обертывать ошибку в unchecked CompletionException (но она все еще завернута).

Ответ 4

Ответы здесь помогли мне устранить исключение в CompletableFuture, используя метод "exceptionnaly" , но он пропустил базовый пример, так что вот один, вдохновленный ответом Marco13:

/**
 * Make a future launch an exception in the accept.
 *
 * This will simulate:
 *  - a readPage service called asynchronously that return a String after 1 second
 *  - a call to that service that uses the result then throw (eventually) an exception, to be processed by the exceptionnaly method.
 *
 */
public class CompletableFutureTest2
{
    public static void main(String[] args)
        throws InterruptedException, ExecutionException
    {
        CompletableFuture<String> future = readPage();

        CompletableFuture<Void> future2 = future.thenAccept(page->{
            System.out.println(page);
            throw new IllegalArgumentException("unexpected exception");
        });

        future2.exceptionally(e->{
          e.printStackTrace(System.err);
          return null;
        });

    }

    private static CompletableFuture<String> readPage()
    {

      CompletableFuture<String> future = new CompletableFuture<>();
      new Thread(()->{
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        // FUTURE: normal process
        future.complete("page");

      }).start();
        return future;
    }


}

Ошибка, которую следует избегать, - вызывать "exceptionnaly" в первом будущем (переменное будущее в моем коде) вместо будущего, возвращаемого "thenAccept", который содержит лямбду, которая может генерировать исключение (переменная future2 в моем код). .

Ответ 5

Как обычно, понимание поведения CompletableFuture лучше оставить в официальных документах и ​​блоге.

Каждый then...() метод цепочки класса CompletableFuture, который реализует CompletionStage, принимает аргумент a CompletionStage, Прошедшая стадия зависит от того, какой порядок методов then...() вы скопировали. Опять же, документы, но здесь что вышеупомянутый блог.