Java Async HttpClient запрос, кажется, блокирует основной поток?

#java #multithreading #asynchronous #java-http-client

#java #многопоточность #асинхронный #java-http-клиент

Вопрос:

В соответствии с этим следующий фрагмент должен быть асинхронным.

Следовательно, вывод должен гласить: TP1, TP2, TP3, http://openjdk.java.net /.

Однако, когда я его запускаю, я получаю: TP1, TP2, http://openjdk.java.net /, TP3.

Кажется, «SendAsync» блокирует основной поток. Это не то, что я ожидал от асинхронного метода.

Я делаю что-то не так?

  public static void main(String[] args) {

    HttpClient client = HttpClient.newHttpClient();

    System.out.println("TP1");

    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://openjdk.java.net/"))
            .build();

    System.out.println("TP2");

    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::uri)
            .thenAccept(System.out::println)
            .join();

    System.out.println("TP3");

}
  

Ответ №1:

Объяснение

Вы вызываете join() , и это явно будет ждать и блокировать, пока не будет завершено будущее.

Из CompletableFuture#join:

Возвращает значение результата по завершении или выдает (непроверенное) исключение, если выполнено в исключительных случаях. […]

Хотя это явно не указано, но очевидно из названия (см. Thread#join, который «Ожидает завершения этого потока».), Он может возвращать результат, только ожидая завершения вызова.

Метод очень похож на CompletableFuture#get, они отличаются своим поведением в отношении исключительного завершения:

Ожидает, если необходимо, завершения этого будущего, а затем возвращает его результат.


Решение

Поместите будущее в переменную и присоединитесь позже, когда вы действительно захотите его дождаться.

Например:

 System.out.println("TP2");

var task = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::uri)
        .thenAccept(System.out::println);

System.out.println("TP3");

task.join(); // wait later
  

Или никогда не ждите этого. Тогда ваш основной поток может умереть раньше, но JVM завершает работу только после того, как все недемонические потоки отключены, а поток, используемый HttpClient для асинхронной задачи, не является потоком демона.


Примечание

Кроме того, никогда не полагайтесь на порядок многопоточного выполнения.

Даже если бы вы не допустили ошибки, порядок, который вы наблюдаете, был бы допустимым порядком многопоточного выполнения.

Помните, что планировщик ОС волен решать, в каком порядке он что выполняет — это может быть любой порядок.

Комментарии:

1. Есть ли способ вызвать событие, когда заголовки и тело были получены. Я надеялся получить данные URL и прервать основной поток, когда данные будут готовы. Аналогично объекту XMLHttpRequest в JavaScript. Отличный ответ и большое спасибо.

2. Конечно, вы можете compose выполнять задачи на будущее. Вы уже делаете это с thenApply помощью and thenAccept . Вы можете создавать столько вещей поверх будущего, сколько захотите.

3. Если вы хотите что-то сделать при получении заголовков ответа, вы можете предоставить свой собственный BodyHandler , который может быть только тонкой оболочкой вокруг встроенного BodyHandler . Просто убедитесь, что не выполняете никаких блокирующих операций в BodyHandler обратных вызовах, так как это может заблокировать HttpClient поток и может помешать обработке всего ответа.