Invoking methods asynchronously using @ Async in Spring

  • 2021-12-05 06:20:23
  • OfStack

Directory uses @ Async asynchronous call method Async brief introduction to TaskExecutor of Spring to complete this record Async usage scenario the difference between asynchronous request and asynchronous call the implementation of asynchronous request the use of asynchronous call in SpringBoot

Invoke methods asynchronously using @ Async

Introduction to Async

Asynchronous method call usage scenarios: processing logs, sending emails, short messages......

@ Async is provided in spring to implement asynchronous methods.

If @ Async decorates a class, all methods of the class are asynchronous, and if @ Async decorates a method, the method is asynchronous.

When the decorated method is called, it will be executed in a new thread.

In Spring, methods are invoked asynchronously by setting the @ Async annotation on them. That is, the method will return immediately when it is called, and the actual execution of this method will cross

Complete TaskExecutor for Spring

1. If the number of threads in the pool is less than corePoolSize at this time, even if all the threads in the pool are idle, create a new thread to handle the added task.

2. If the number in the thread pool is equal to corePoolSize, but the buffer queue workQueue is not full, then the task is put into the buffer queue.

3. If the number in the thread pool is greater than corePoolSize, the buffer queue workQueue is full, and the number in the thread pool is less than maxPoolSize, create a new thread to handle the added task.

4. If the number in the thread pool is greater than corePoolSize, the buffer queue workQueue is full, and the number in the thread pool is equal to maxPoolSize, then the task is handled by the policy specified by handler. That is, the priority of processing tasks is: core thread corePoolSize, task queue workQueue and maximum thread maximumPoolSize. If all three are full, handler is used to process rejected tasks.

5. When the number of threads in the thread pool is greater than corePoolSize, if a thread is idle for more than keepAliveTime, the thread will be terminated. In this way, the thread pool can dynamically adjust the number of threads in the pool.

Record the usage scenario of Async this time

Other services need to be called, and the main thread needs to continue to complete the current thread task

Step 1: Classes that need to do things


@Component
@EnableScheduling
public class VideoStatusUpdateServiceImpl implements VideoStatusUpdateService { 
    @Resource
    private VaCaseVideoExtMapper vaCaseVideoExtMapper;  
    // Every interval 5 Seconds 
    @Scheduled(cron = "*/5 * * * * ? ")
    @Override
    public void videoStatusUpdate() throws IOException {
        // Get 1 Set of 
        List<VaCaseVideo> list = vaCaseVideoExtMapper.selectAllVideoes();
        // Traverse the collection to create asynchronous threads to do 1 Something else 
        for (VaCaseVideo vo : list) {
            dealTask(vo);
        }
    } 
    @Async("asyncServiceExecutor")
    public void dealTask(VaCaseVideo vo) throws IOException {
       System.out.print(" Something is being done here ")
    }   
}

Step 2: Start the class with the annotation @ EnableAsync to start asynchronous


@SpringBootApplication
@EnableAsync
@EnableCaching
public class StartApp { 
 public static void main(String[] args) {
  SpringApplication.run(StartApp.class, args);
 }
}

Step 3: Configure Executor (this step is optional, and the default value will be used if the value is not matched) and configure the custom Executor


@Configuration
public class ExecutorConfig { 
    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class); 
    @Bean
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Configure the number of core threads 
        executor.setCorePoolSize(5);
        // Configure the maximum number of threads 
        executor.setMaxPoolSize(60);
        executor.setKeepAliveSeconds(180);
        // Configure queue size 
        executor.setQueueCapacity(60);
        // Configure the name prefix of threads in the thread pool 
        executor.setThreadNamePrefix("async-service-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // Perform initialization 
        executor.initialize();
        return executor;
    }
}

Step 4: Start the project and print what needs to be done every 5 seconds

Difference between asynchronous request and asynchronous call

The usage scenarios of the two are different. Asynchronous request is used to solve the pressure caused by concurrent requests to the server, thus improving the throughput of requests; Asynchronous call is used to do some tasks that are not the main flow and do not need real-time calculation and response, such as synchronizing logs to kafka for log analysis.

The asynchronous request will wait for the corresponding response and need to return the result to the client; And asynchronous call we often immediately return to the client response, complete the entire request, as for asynchronous call task background to run slowly, the client will not care.

Implementation of asynchronous request

Mode 1: Servlet to realize asynchronous request


  @RequestMapping(value = "/email/servletReq", method = GET)
  public void servletReq (HttpServletRequest request, HttpServletResponse response) {
      AsyncContext asyncContext = request.startAsync();
      // Set up the listener : You can set the callback handling of its start, finish, exception, timeout and other events 
      asyncContext.addListener(new AsyncListener() {
          @Override
          public void onTimeout(AsyncEvent event) throws IOException {
              System.out.println(" Time out ...");
              // Do 1 Some related actions after timeout ...
          }
          @Override
          public void onStartAsync(AsyncEvent event) throws IOException {
              System.out.println(" Thread start ");
          }
          @Override
          public void onError(AsyncEvent event) throws IOException {
              System.out.println(" An error occurred: "+event.getThrowable());
          }
          @Override
          public void onComplete(AsyncEvent event) throws IOException {
              System.out.println(" Execution completion ");
              // You can do it here 1 Some operations to clean up resources ...
          }
      });
      // Set timeout time 
      asyncContext.setTimeout(20000);
      asyncContext.start(new Runnable() {
          @Override
          public void run() {
              try {
                  Thread.sleep(10000);
                  System.out.println(" Internal threads: " + Thread.currentThread().getName());
                  asyncContext.getResponse().setCharacterEncoding("utf-8");
                  asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                  asyncContext.getResponse().getWriter().println(" This is an asynchronous request return ");
              } catch (Exception e) {
                  System.out.println(" Exception: "+e);
              }
              // Asynchronous request completion notification 
              // At this point, the whole request is completed 
              asyncContext.complete();
          }
      });
      // At this time and so on  request The thread connection of has been released 
      System.out.println(" Main thread: " + Thread.currentThread().getName());
  }

Mode 2: It is very simple to use. The parameters returned directly can be wrapped in layer 1 callable, and the default thread pool and timeout processing can be set by inheriting WebMvcConfigurerAdapter class


  @RequestMapping(value = "/email/callableReq", method = GET)
  @ResponseBody
  public Callable<String> callableReq () {
      System.out.println(" External threads: " + Thread.currentThread().getName()); 
      return new Callable<String>() { 
          @Override
          public String call() throws Exception {
              Thread.sleep(10000);
              System.out.println(" Internal threads: " + Thread.currentThread().getName());
              return "callable!";
          }
      };
  }
 
  @Configuration
  public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {
 
  @Resource
  private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;
 
  @Override
  public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
      // Deal with  callable Timeout 
      configurer.setDefaultTimeout(60*1000);
      configurer.setTaskExecutor(myThreadPoolTaskExecutor);
      configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
  }
 
  @Bean
  public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
      return new TimeoutCallableProcessingInterceptor();
  }
}

Mode 3: Similar to Mode 2, outsource Layer 1 in Callable, and set a timeout callback for WebAsyncTask to realize timeout processing


   @RequestMapping(value = "/email/webAsyncReq", method = GET)
    @ResponseBody
    public WebAsyncTask<String> webAsyncReq () {
        System.out.println(" External threads: " + Thread.currentThread().getName());
        Callable<String> result = () -> {
            System.out.println(" Internal thread starts: " + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (Exception e) {
                // TODO: handle exception
            }
            logger.info(" Secondary thread returns ");
            System.out.println(" Internal threads return: " + Thread.currentThread().getName());
            return "success";
        };
        WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);
        wat.onTimeout(new Callable<String>() {
 
            @Override
            public String call() throws Exception {
                // TODO Auto-generated method stub
                return " Timeout ";
            }
        });
        return wat;
    }

Mode 4: DeferredResult can deal with 1 relatively complex business logic, and the most important thing is to process and return business in another thread, that is, to communicate between two completely unrelated threads.


@RequestMapping(value = "/email/deferredResultReq", method = GET)
    @ResponseBody
    public DeferredResult<String> deferredResultReq () {
        System.out.println(" External threads: " + Thread.currentThread().getName());
        // Set timeout time 
        DeferredResult<String> result = new DeferredResult<String>(60*1000L);
        // Handling timeout events   Adopt the entrustment mechanism 
        result.onTimeout(new Runnable() {
 
            @Override
            public void run() {
                System.out.println("DeferredResult Timeout ");
                result.setResult(" Time out !");
            }
        });
        result.onCompletion(new Runnable() {
 
            @Override
            public void run() {
                // After completion 
                System.out.println(" Call complete ");
            }
        });
        myThreadPoolTaskExecutor.execute(new Runnable() {
 
            @Override
            public void run() {
                // Handle business logic 
                System.out.println(" Internal threads: " + Thread.currentThread().getName());
                // Return results 
                result.setResult("DeferredResult!!");
            }
        });
       return result;
    }

The Use of Asynchronous Call in SpringBoot

1. Introduction

Processing of asynchronous requests. In addition to asynchronous requests, 1 generally, we should use asynchronous calls. Usually, in the development process, you will encounter a method that has nothing to do with the actual business and has no compactness. For example, recording log information and other services. This time normal is to start a new thread to do 1 business processing, so that the main thread asynchronous execution of other business.

2. Usage mode (based on spring)

You need to add @ EnableAsync to the startup class to make the asynchronous call @ Async annotation effective

Add this annotation to the method that needs to be executed asynchronously to @ Async ("threadPool"), and threadPool is a custom thread pool

The code is omitted. . . Just two labels, just try one by yourself

3. Precautions

By default, when TaskExecutor is not set, SimpleAsyncTaskExecutor is used by default, but this thread is not a thread pool in the true sense, because threads are not reused, and a new thread is created every time. As can be seen from the console log output, the thread name is incremented every time. Therefore, it is best for us to customize 1 thread pool.

The asynchronous method called cannot be the method of the same class (including the internal class of the same class). Simply put, because Spring will create a proxy class for it when starting scanning, and when calling the same kind, it still calls its own proxy class, so it is the same as ordinary calling.

Other annotations, such as @ Cache, are also the same reason. To put it bluntly, it is caused by the proxy mechanism of Spring. Therefore, in development, it is best to separate asynchronous services into one class to manage. I will focus on it below.

4. What causes the @ Async asynchronous method to fail?

a. Call the same asynchronous method with @ Async betting on the same class: In spring, annotations like @ Async and @ Transactional, cache essentially use dynamic proxies. In fact, when the Spring container is initialized, the Spring container will "replace" the class object containing AOP annotations with proxy objects (simply understood), so the reason for the annotation failure is obvious, that is, it is the object itself that calls the method instead of the proxy object, and because it does not pass through the Spring container, then the solution will be solved along this line of thinking. b. The static (static) method is called c. Call (private) privatize method

5. The way to solve problem 1 in 4 (just pay attention to the other 2 and 3 problems)

The method that will be executed asynchronously is extracted into a class separately. The principle is that when you extract the method that executes asynchronously into a class separately, this class must be managed by Spring, and other Spring components will be injected when they need to be called. At this time, the proxy class is actually injected.

In fact, all our injected objects assign member variables to the current Spring component from the Spring container. Because some classes use AOP annotations, what actually exists in the Spring container is its proxy object. Then we can get our own proxy object through the context and call asynchronous methods.


@Controller
@RequestMapping("/app")
public class EmailController { 
    // Get ApplicationContext There are many ways to object , This is the simplest , Everyone else knows for themselves 1 Under 
    @Autowired
    private ApplicationContext applicationContext;
 
    @RequestMapping(value = "/email/asyncCall", method = GET)
    @ResponseBody
    public Map<String, Object> asyncCall () {
        Map<String, Object> resMap = new HashMap<String, Object>();
        try{
            // It doesn't work to call asynchronous methods under the same kind 
            //this.testAsyncTask();
            // Get your own proxy object through context and call asynchronous methods 
            EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);
            emailController.testAsyncTask();
            resMap.put("code",200);
        }catch (Exception e) {
            resMap.put("code",400);
            logger.error("error!",e);
        }
        return resMap;
    }
 
    // Attention 1 It must be public, And right and wrong static Method 
    @Async
    public void testAsyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println(" Asynchronous task execution completed! ");
    } 
}

6. Open the cglib proxy and manually obtain the Spring proxy class, so as to call asynchronous methods under the same kind.

First, add the @ EnableAspectJAutoProxy (exposeProxy = true) annotation to the startup class.

Code implementation, as follows:


@Service
@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
public class EmailService {
 
    @Autowired
    private ApplicationContext applicationContext;
 
    @Async
    public void testSyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println(" Asynchronous task execution completed! ");
    } 
    public void asyncCallTwo() throws InterruptedException {
        //this.testSyncTask();
//        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
//        emailService.testSyncTask();
        boolean isAop = AopUtils.isAopProxy(EmailController.class);// Whether it is a proxy object; 
        boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  // Whether it is CGLIB The proxy object of the mode; 
        boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  // Whether it is JDK Proxy object in dynamic proxy mode; 
        // Here's the point !!!
        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
        EmailService proxy = (EmailService) AopContext.currentProxy();
        System.out.println(emailService == proxy ? true : false);
        proxy.testSyncTask();
        System.out.println("end!!!");
    }
}

Related articles: