# Предисловие
На основании вышеизложенного@AsyncаннотированныйПрограммирование асинхронных вызовов, эта статья будет продолжать знакомитьSpring BootизWebAsyncTaskдля более гибкой асинхронной обработки задач, в том числеАсинхронный обратный вызов,Обработка тайм-аутаиОбработка исключений.
Статьи из этой серии
- Actual Spring Boot 2.0 Series (1) — Создание образов Docker с помощью Gradle
- Actual Spring Boot 2.0 Series (2) — глобальная обработка исключений и тестирование
- Actual Spring Boot 2.0 Series (3) — Подробное объяснение асинхронных вызовов с использованием @Async
- Actual Spring Boot 2.0 Series (4) — Использование WebAsyncTask для обработки асинхронных задач
- Actual Spring Boot 2.0 Series (5) — прослушиватель, сервлет, фильтр и перехватчик
- Actual Spring Boot 2.0 Series (6) — несколько реализаций одномашинных задач синхронизации
текст
1. Обработка ниток и асинхронная нить
Перед выполнением следующего объяснения, где первое различие по двум понятиям:
-
Поток обработки:поток обработкипринадлежать
webсерверный поток, отвечающий заОбработка запросов пользователей,использоватьПул потоковуправлять. -
Асинхронный поток:Асинхронный потокпринадлежатьПользовательский поток, который можно использоватьуправление пулом потоков.
Springпредусмотреноасинхронная задача API,использоватьWebAsyncTaskкласс для реализацииасинхронная задача. Установите соответствующую асинхронную задачуобработка обратного вызова, как когдатайм-аут задачи,выброшено исключениеЖдать. Асинхронные задачи обычно очень практичны, например, когда заказ оплачен, запустите асинхронную задачу, чтобы запросить результат оплаты заказа.
2. Подготовка окружающей среды
Настройка зависимостей градиента
использоватьSpring InitializerСоздаватьgradleпроектspring-boot-web-async-task, добавляйте связанные зависимости при создании. получить начальныйbuild.gradleследующее:
buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'io.ostenant.springboot.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Настроить класс обслуживания
Настройка планировщика для асинхронных задачMockСлужить.
@Service
public class WebAsyncService {
public String generateUUID() {
return UUID.randomUUID().toString();
}
}
Настройте контроллер асинхронной обработки и внедрите указанную выше службу.Bean.
@RestController
public class WebAsyncController {
private final WebAsyncService asyncService;
private final static String ERROR_MESSAGE = "Task error";
private final static String TIME_MESSAGE = "Task timeout";
@Autowired
public WebAsyncController(WebAsyncService asyncService) {
this.asyncService = asyncService;
}
}
3. Обычные асинхронные задачи
настроить нормальныйWebAsyncTaskобъект задачи, поставить задачусверхурочное времяза10s. Асинхронное выполнение задач принимаетThread.sleep(long)Моделирование, установить здесьАсинхронный потоквремя сна5s.
@GetMapping("/completion")
public WebAsyncTask<String> asyncTaskCompletion() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName()));
// 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep(5 * 1000L);
return asyncService.generateUUID();
});
// 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
out.println("继续处理其他事情");
return asyncTask;
}
запускатьSpring Bootпроект, посетитеhttp://localhost:8080/completion, положить началонормальныйасинхронный запрос задачи.
Наблюдайте за выводом консоли, вы можете проверитьWebAsyncTaskПоток асинхронной обработки является нормальным.
请求处理线程:http-nio-8080-exec-2
继续处理其他事情
异步工作线程:MvcAsync1
任务执行完成
WebСтраница отвечает нормально, и ответное сообщение страницы выглядит следующим образом:
Примечание. WebAsyncTask.onCompletion(Runnable): после завершения выполнения текущей задачи, независимо от того, было ли выполнение успешным или прервано, в конечном итоге будет вызван обратный вызов onCompletion.
4. Генерация исключений для асинхронных задач
настроитьОшибкаизWebAsyncTaskобъект задачи, поставить задачусверхурочное времяза10s. в методе асинхронного выполнения задачиВыбросить исключение.
@GetMapping("/exception")
public WebAsyncTask<String> asyncTaskException() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName()));
// 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep(5 * 1000L);
throw new Exception(ERROR_MESSAGE);
});
// 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
asyncTask.onError(() -> {
out.println("任务执行异常");
return ERROR_MESSAGE;
});
out.println("继续处理其他事情");
return asyncTask;
}
запускатьSpring Bootпроект, посетитеhttp://localhost:8080/exception, положить началоаномальныйасинхронный запрос задачи.
WebИнформация об исключении ответа на страницу выглядит следующим образом:
Наблюдайте за выводом консоли, вы можете проверитьWebAsyncTaskзаненормальный запросасинхронная обработка.
请求处理线程:http-nio-8080-exec-1
继续处理其他事情
异步工作线程:MvcAsync2
2018-06-18 21:12:10.110 ERROR 89875 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception
java.lang.Exception: Task error
at io.ostenant.springboot.sample.controller.WebAsyncController.lambda$asyncTaskException$2(WebAsyncController.java:55) ~[classes/:na]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$4(WebAsyncManager.java:317) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_172]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_172]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_172]
2018-06-18 21:12:10.111 ERROR 89875 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.Exception: Task error] with root cause
java.lang.Exception: Task error
at io.ostenant.springboot.sample.controller.WebAsyncController.lambda$asyncTaskException$2(WebAsyncController.java:55) ~[classes/:na]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$4(WebAsyncManager.java:317) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_172]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_172]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_172]
任务执行异常
2018-06-18 21:12:10.144 WARN 89875 --- [nio-8080-exec-2] o.apache.catalina.core.AsyncContextImpl : onError() failed for listener of type [org.apache.catalina.core.AsyncListenerWrapper]
java.lang.IllegalArgumentException: Cannot dispatch without an AsyncContext
at org.springframework.util.Assert.notNull(Assert.java:193) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.dispatch(StandardServletAsyncWebRequest.java:131) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.setConcurrentResultAndDispatch(WebAsyncManager.java:353) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$2(WebAsyncManager.java:304) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError$0(StandardServletAsyncWebRequest.java:146) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at java.util.ArrayList.forEach(ArrayList.java:1257) ~[na:1.8.0_172]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onError(StandardServletAsyncWebRequest.java:146) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.apache.catalina.core.AsyncListenerWrapper.fireOnError(AsyncListenerWrapper.java:49) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:397) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:239) [tomcat-embed-core-8.5.31.jar:8.5.31]
at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:232) [tomcat-embed-core-8.5.31.jar:8.5.31]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53) [tomcat-embed-core-8.5.31.jar:8.5.31]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) [tomcat-embed-core-8.5.31.jar:8.5.31]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468) [tomcat-embed-core-8.5.31.jar:8.5.31]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.31.jar:8.5.31]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_172]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_172]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.31.jar:8.5.31]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_172]
任务执行完成
Примечание. WebAsyncTask.onError(Callable>): когда асинхронная задача выдает исключение, вызывается метод onError().
5. Время ожидания асинхронных задач
настроить нормальныйWebAsyncTaskобъект задачи, поставить задачусверхурочное времяза10s. Асинхронное выполнение задач принимаетThread.sleep(long)Моделирование, установить здесьАсинхронный потоквремя сна15s, что приводит к тайм-ауту асинхронной задачи.
@GetMapping("/timeout")
public WebAsyncTask<String> asyncTaskTimeout() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName()));
// 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep(15 * 1000L);
return TIME_MESSAGE;
});
// 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
asyncTask.onTimeout(() -> {
out.println("任务执行超时");
return TIME_MESSAGE;
});
out.println("继续处理其他事情");
return asyncTask;
}
запускатьSpring Bootпроект, посетитеhttp://localhost:8080/timeout, положить началотайм-аутасинхронный запрос задачи.
Наблюдайте за выводом консоли, вы можете проверитьWebAsyncTaskПроцесс обработки асинхронного тайм-аута.
请求处理线程:http-nio-8080-exec-1
继续处理其他事情
异步工作线程:MvcAsync3
任务执行超时
任务执行完成
WebСтраница часто отвечает на сообщение о тайм-ауте, и ответное сообщение страницы выглядит следующим образом:
Примечание: webasynctask.ontimeout (Callable ): Когда асинхронная задача - это время ожидания, это метод onTimeout ().
6. Асинхронные задачи пула потоков
из трех случаев вышеасинхронная задачаПо умолчанию не использоватьмеханизм пула потоковудалось.
То есть, когда приходит запрос, хотя поток обработки освобождается, система все равно создает запрос.Поток асинхронной задачи, что видно вышеMvcAsyncначалоПоток асинхронной задачи.
Следствием этого является то, что накладные расходы являются серьезными, поэтому обычно используетсяПул потоковЕдиное управление, прямо вWebAsyncTaskКонструктор класса проходит черезThreadPoolTaskExecutorэкземпляр объекта.
Создание пула потоковBeanОбъект:
@Configuration
public class TaskConfiguration {
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(10);
taskExecutor.setThreadNamePrefix("asyncTask");
return taskExecutor;
}
}
Вводить в контроллерThreadPoolTaskExecutorобъект, реконфигурированный на основеПул потоковизАсинхронная обработка задач.
@Autowired
@Qualifier("taskExecutor")
private ThreadPoolTaskExecutor executor;
@GetMapping("/threadPool")
public WebAsyncTask<String> asyncTaskThreadPool() {
return new WebAsyncTask<>(10 * 1000L, executor,
() -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
return asyncService.generateUUID();
});
}
запросить одновременноhttp://localhost:8080/threadPool, наблюдайте за выводом консолиАсинхронный потокинформацию можно найтиасинхронная задачаНепосредственно изПул потоковполучено вАсинхронный поток.
异步工作线程:asyncTask1
异步工作线程:asyncTask2
异步工作线程:asyncTask3
异步工作线程:asyncTask4
异步工作线程:asyncTask5
异步工作线程:asyncTask1
异步工作线程:asyncTask2
异步工作线程:asyncTask3
异步工作线程:asyncTask4
异步工作线程:asyncTask5
резюме
В этой статье описываетсяSpring Bootкоторый предоставилWebAsyncTaskасинхронное программированиеAPI. По сравнению с вышеуказанным@Asyncаннотация,WebAsyncTaskобеспечить более надежноеОбработка тайм-аутаиОбработка исключенийслужба поддержки.
Добро пожаловать в технический публичный аккаунт: Zero One Technology Stack
Эта учетная запись будет продолжать делиться сухими товарами серверных технологий, включая основы виртуальных машин, многопоточное программирование, высокопроизводительные фреймворки, асинхронное ПО, промежуточное ПО для кэширования и обмена сообщениями, распределенные и микросервисы, материалы для обучения архитектуре и расширенные учебные материалы и статьи.