# introduction

In this paper, we will continue to introduce Spring Boot WebAsyncTask for more flexible asynchronous task handling, including asynchronous callback, timeout handling and exception handling.

This series of articles

  1. Spring Boot 2.0 series 1 – Build Docker images using Gradle
  2. Spring Boot 2.0 Series ii – Global exception handling and testing
  3. Spring Boot 2.0 series (3) – Using @async to make asynchronous calls
  4. Spring Boot 2.0 series 4 – Use WebAsyncTask to handle asynchronous tasks
  5. Spring Boot 2.0 series 5 – Listener, Servlet, Filter, and Interceptor
  6. Spring Boot 2.0 series (vi) – Several implementations of single machine timing tasks

The body of the

1. Handle threads and asynchronous threads

Before we begin, let’s make a distinction between two concepts:

  1. Processing threads: Processing threads are Web server threads that handle user requests and are managed by thread pools.

  2. Asynchronous threads: Asynchronous threads are user-defined threads that can be managed by thread pools.

Spring provides an API for asynchronous tasks, and WebAsyncTask can be used to implement asynchronous tasks. Set the corresponding callback processing for asynchronous tasks, such as when the task times out or an exception is thrown. Asynchronous tasks are often very useful. For example, when an order is paid, enable the asynchronous task to query the payment result of the order.

2. Prepare the environment

Configure gradle dependencies

Create a Gradle project spring-boot-web-async-task using Spring Initializer. Add dependencies when creating gradle project spring-boot-web-async-Task. The resulting initial build.gradle looks like this:

buildscript {
    ext {
        springBootVersion = '. The 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 - the SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')}Copy the code

Configuring service Classes

Configure a Mock service for asynchronous task scheduling.

@Service
public class WebAsyncService {
    public String generateUUID(a) {
        returnUUID.randomUUID().toString(); }}Copy the code

Configure the asynchronous processing controller and inject the above service beans.

@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; }}Copy the code

3. Normal asynchronous tasks

Configure a normal WebAsyncTask task object and set the task timeout period to 10s. The asynchronous task execution is simulated by Thread.sleep(long), where the sleep time of the asynchronous Thread is set to 5s.

@GetMapping("/completion")
public WebAsyncTask<String> asyncTaskCompletion(a) {
    // Prints the processing thread name
    out.println(format("Request processing thread: %s", currentThread().getName()));

    // Start an asynchronous task. The timeout period is 10 seconds
    WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
        out.println(format("Asynchronous worker thread: %s", currentThread().getName()));
        // The task processing time is 5s without timeout
        sleep(5 * 1000L);
        return asyncService.generateUUID();
    });

    // This method is called when the task is completed
    asyncTask.onCompletion(() -> out.println("Mission completed."));
    out.println("Move on to other matters.");
    return asyncTask;
}
Copy the code

Start the Spring Boot program, visit http://localhost:8080/completion, a normal asynchronous task request.

By looking at the console output, you can verify that WebAsyncTask’s asynchronous processing flow is normal.

Request processing thread: HTTP-niO-8080-exec-2 Continues processing other things Asynchronous worker thread: MvcAsync1 Task execution is completeCopy the code

The Web page responds as follows:

Note: WebAsyncTask. OnCompletion (Runnable) : after the end of the current task execution, execution success or abort, onCompletion callback can eventually be invoked.

4. Throw abnormal asynchronous tasks

Configure an incorrect WebAsyncTask task object and set the task timeout period to 10s. Throw an exception in an asynchronous task execution method.

@GetMapping("/exception")
public WebAsyncTask<String> asyncTaskException(a) {
    // Prints the processing thread name
    out.println(format("Request processing thread: %s", currentThread().getName()));

    // Start an asynchronous task. The timeout period is 10 seconds
    WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
        out.println(format("Asynchronous worker thread: %s", currentThread().getName()));
        // The task processing time is 5s without timeout
        sleep(5 * 1000L);
        throw new Exception(ERROR_MESSAGE);
    });

    // This method is called when the task is completed
    asyncTask.onCompletion(() -> out.println("Mission completed."));
    asyncTask.onError(() -> {
        out.println("Task execution exception");
        return ERROR_MESSAGE;
    });

    out.println("Move on to other matters.");
    return asyncTask;
}
Copy the code

Start the Spring Boot program, visit http://localhost:8080/exception, launched a abnormal asynchronous task request.

The following information is displayed on the Web page:

By looking at the console output, you can verify WebAsyncTask’s asynchronous handling of an exception request.

Request processing thread: HTTP-niO-8080-exec-1 To continue processing other things asynchronous worker thread: 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. The jar: 5.0.7. RELEASE] at Java. Util. Concurrent. The Executors$RunnableAdapterCall (Executors. Java: 511) ~ [na: 1.8.0 comes with _172] at Java. Util. Concurrent. FutureTask. Run (FutureTask. Java: 266) ~ [na: 1.8.0 comes with _172] the 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. The jar: 5.0.7. RELEASE] at Java. Util. Concurrent. The Executors$RunnableAdapterCall (Executors. Java: 511) ~ [na: 1.8.0 comes with _172] at Java. Util. Concurrent. FutureTask. Run (FutureTask. Java: 266) ~ [na: 1.8.0 comes with _172] the at Java.lang.thread. run(thread.java :748) [na:1.8.0_172] WARN 89875 -- [NIO-8080-EXEC-2] o.apache.catalina.core.AsyncContextImpl : onError() failedfor 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. The jar: 5.0.7. RELEASE] the at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.dispatch(StandardServletAsyncWebRequest.jav A: 131) ~ [spring - web - 5.0.7. The jar: 5.0.7. RELEASE] the at org.springframework.web.context.request.async.WebAsyncManager.setConcurrentResultAndDispatch(WebAsyncManager.java:353) ~ [spring - web - 5.0.7. RELEASE. The jar: 5.0.7. RELEASE] at org. Springframework. Web. Context. Request. The async. WebAsyncManager. Lambda$startCallableProcessing$2(WebAsyncManager. Java: 304) ~ [spring - web - 5.0.7. The jar: 5.0.7. RELEASE] the at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError$0(StandardServletAsyncWebRequest. Java: 146) ~ [spring - web - 5.0.7. The jar: 5.0.7. RELEASE] the at Java. Util. ArrayList. ForEach (ArrayList. Java: 1257) ~ [na: 1.8.0 comes with _172] the at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onError(StandardServletAsyncWebRequest.java : 146) ~ [spring - web - 5.0.7. The jar: 5.0.7. RELEASE] the at org.apache.catalina.core.AsyncListenerWrapper.fireOnError(AsyncListenerWrapper.java:49) ~ [tomcat - embed - core - 8.5.31. Jar: 8.5.31] the at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:397) ~ [tomcat - embed - core - 8.5.31. Jar: 8.5.31] the at Org. Apache. Catalina. Connector. CoyoteAdapter. AsyncDispatch (CoyoteAdapter. Java: 239) [tomcat embed - core - 8.5.31. Jar: 8.5.31] The at org. Apache. Coyote. AbstractProcessor. Dispatch (AbstractProcessor. Java: 232) [tomcat embed - core - 8.5.31. Jar: 8.5.31] the at Org. Apache. Coyote. AbstractProcessorLight. Process (53) AbstractProcessorLight. Java: [tomcat embed - core - 8.5.31. Jar: 8.5.31] at org.apache.coyote.AbstractProtocol$ConnectionHandler. The process (AbstractProtocol. Java: 790) [tomcat embed - core - 8.5.31. Jar: 8.5.31] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessorDoRun (NioEndpoint. Java: 1468) [tomcat embed - core - 8.5.31. Jar: 8.5.31] the at Org.apache.tomcat.util.net.SocketProcessorBase.run (SocketProcessorBase. Java: 49) [tomcat embed - core - 8.5.31. Jar: 8.5.31] the at Java. Util. Concurrent. ThreadPoolExecutor. RunWorker (ThreadPoolExecutor. Java: 1149) [na: 1.8.0 comes with _172] the at java.util.concurrent.ThreadPoolExecutor$Worker. The run (ThreadPoolExecutor. Java: 624) [na: 1.8.0 comes with _172] at org.. Apache tomcat. Util. Threads. TaskThread$WrappingRunnableRun (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] Task execution completedCopy the code

Note: WebAsyncTask. OnError (Callable <? >) : The onError() method is called when an asynchronous task throws an exception.

5. Timeout asynchronous tasks

Configure a normal WebAsyncTask task object and set the task timeout period to 10s. The asynchronous task execution is simulated by Thread.sleep(long), where the sleep time of the asynchronous Thread is set to 15s, causing the asynchronous task to timeout.

@GetMapping("/timeout")
public WebAsyncTask<String> asyncTaskTimeout(a) {
    // Prints the processing thread name
    out.println(format("Request processing thread: %s", currentThread().getName()));

    // Start an asynchronous task. The timeout period is 10 seconds
    WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
        out.println(format("Asynchronous worker thread: %s", currentThread().getName()));
        // The task processing time is 5s without timeout
        sleep(15 * 1000L);
        return TIME_MESSAGE;
    });

    // This method is called when the task is completed
    asyncTask.onCompletion(() -> out.println("Mission completed."));
    asyncTask.onTimeout(() -> {
        out.println("Task execution timed out");
        return TIME_MESSAGE;
    });

    out.println("Move on to other matters.");
    return asyncTask;
}
Copy the code

Start the Spring Boot program, visit http://localhost:8080/timeout, initiate asynchronous task request timeout.

Observe the console output to verify the process of WebAsyncTask’s asynchronous timeout processing.

Request processing thread: HTTP-NIO-8080-exec-1 To continue processing other things Asynchronous worker thread: MvcAsync3 task execution timeout task execution completeCopy the code

The Web page often responds to a timeout message. The response message is as follows:

Note: WebAsyncTask. OnTimeout (Callable <? >) : The onTimeout() method is called when an asynchronous task times out.

6. Thread pool asynchronous tasks

Asynchronous tasks in the above three cases are not managed by thread pools by default.

That is, when a request comes in, it frees up the processing thread, but the system still creates an asynchronous task thread for each request, which is the MvcAsync starting asynchronous task thread seen above.

The result is overhead, so thread pools are often used for uniform management, passing in an instance of the ThreadPoolTaskExecutor object directly to the WebAsyncTask class constructor.

Construct a thread pool Bean object:

@Configuration
public class TaskConfiguration {
    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor(a) {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(10);
        taskExecutor.setThreadNamePrefix("asyncTask");
        returntaskExecutor; }}Copy the code

Inject the ThreadPoolTaskExecutor object into the controller to reconfigure asynchronous task processing based on thread pools.

@Autowired
@Qualifier("taskExecutor")
private ThreadPoolTaskExecutor executor;

@GetMapping("/threadPool")
public WebAsyncTask<String> asyncTaskThreadPool(a) {
    return new WebAsyncTask<>(10 * 1000L, executor,
            () -> {
                out.println(format("Asynchronous worker thread: %s", currentThread().getName()));
                return asyncService.generateUUID();
            });
}
Copy the code

To concurrently request http://localhost:8080/threadPool, observe the console output of the asynchronous thread information, asynchronous tasks can be found Asynchronous threads directly obtained from the thread pool.

Asynchronous worker thread: asyncTask1 Asynchronous worker thread: asyncTask2 Asynchronous worker thread: asyncTask3 Asynchronous worker thread: asyncTask4 Asynchronous worker thread: asyncTask5 Asynchronous worker thread: asyncTask1 Asynchronous worker thread: AsyncTask2 Asynchronous worker: asyncTask3 Asynchronous worker: asyncTask4 Asynchronous worker: asyncTask5Copy the code

summary

This article introduced the asynchronous programming API for WebAsyncTask provided by Spring Boot. WebAsyncTask provides more robust timeout and exception handling support than the @async annotation introduced above.


Welcome to pay attention to the technical public number: Zero one Technology Stack

This account will continue to share learning materials and articles on back-end technologies, including virtual machine basics, multithreaded programming, high-performance frameworks, asynchronous, caching and messaging middleware, distributed and microservices, architecture learning and progression.