#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()
, и это явно будет ждать и блокировать, пока не будет завершено будущее.
Возвращает значение результата по завершении или выдает (непроверенное) исключение, если выполнено в исключительных случаях. […]
Хотя это явно не указано, но очевидно из названия (см. 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
помощью andthenAccept
. Вы можете создавать столько вещей поверх будущего, сколько захотите.3. Если вы хотите что-то сделать при получении заголовков ответа, вы можете предоставить свой собственный
BodyHandler
, который может быть только тонкой оболочкой вокруг встроенногоBodyHandler
. Просто убедитесь, что не выполняете никаких блокирующих операций вBodyHandler
обратных вызовах, так как это может заблокироватьHttpClient
поток и может помешать обработке всего ответа.