Kewitschka
Kewitschka

Reputation: 1671

Spring Boot: Async method not running in separate Thread

I want to run a method in a separate thread. Therefore I tried to use @Async but it does not work. The printed output always refers to the same thread id.

MyApp.java

@SpringBootApplication
@EnableAsync
@EnableConfigurationProperties(ConfigProperties.class)
public class MyApp {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(MyApp.class, args);
    }

    @Bean("threadPoolTaskExecutor")
    public TaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(1000);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setThreadNamePrefix("Async-");
        return executor;
    }

}

MyService.java

@Slf4j
@Service
public class MyService implements ISichtServiceBroker {

@Inject
public MyService(Helper helper) {
    this.helper = helper;
}

@Transactional
public void handle(Message message) {
    System.out.println("Execute method synchronously - " + Thread.currentThread().getName());
    handleAsync(message);
}

@Async("threadPoolTaskExecutor")
public void handleAsync(Message message) {
    System.out.println("Execute method asynchronously - " + Thread.currentThread().getName());
}
}

Upvotes: 6

Views: 12163

Answers (3)

Ananthapadmanabhan
Ananthapadmanabhan

Reputation: 6226

First thing : @Async annotations do not work if they are invoked by ‘this’ . From official doc :

The method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, the SimplePojo reference in this case, any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy

Adding to what @Kavithakaran provided as answer, the issue with your code is that you are trying to use @Async and @Transactional. You have an asynchronous method getting triggered from a transactional one in the same class .

When using @Async annotation the Spring framework will use AOP (Aspect Oriented Programming) on the Spring proxy of the @Component to wrap calls to this method in a Runnable and schedule this Runnable on a task executor. Similarly, @Transactional also uses AOP and proxying. In a Transactional method normally without a Async the transaction gets propagated through the call hierarchy from one Spring @Component to the other.

But like in your case when an @Transactional method calls an @Async one this does not happen since the async method is being called from the same class, it uses the same thread. Probably because of the fact that @Async annotations do not work if they are invoked by ‘this’. @Async behaves just like the @Transactional annotation.

Also, In order to make Spring manage the transaction of the @Async method either the @Component or the method itself should declare the @Transactional annotation, this way Spring will manage the transaction even if a method is being executed asynchronous.So , like the previous answer my suggestion would be to extract the async method to a separate component make it transactional and use the aync method on that component class method. Have async method like :

@Slf4j
@Transactional
@Component
public class AsyncClass {

  @Async("threadPoolTaskExecutor")
  public void handleAsync(Message message) {
      System.out.println("Execute method asynchronously - " + 
      Thread.currentThread().getName());
  }

}

Upvotes: 6

Sardaar54
Sardaar54

Reputation: 46

If you look at the @Async description in the below documentation, it specifies that self-invocation of @Async methods will not work when called from the same class. Looks like you are facing a similar issue.

https://www.baeldung.com/spring-async

Upvotes: 1

  • Spring achieves desired effect of @Transactional and @Async by creating proxies. so when you inject MyService, you are actually injecting a proxy of MyService which wraps your original class instance. So if the calls go through this proxy, then those annotations will work and if the method get called directly without the proxy, it won't work.

  • In your case, It is happening like this ProxyMyService.handle() --> MyService.handle() --> MyService.handleAsync() while you want ProxyMyService.handle() --> MyService.handle() --> ProxyMyService. handleAsync() -->MyService.handleAsync()

  • In order to achieve that, you should create two service classes and put one method in class and second method in another

Note

And also when you do that in two different services, even though handleAsync will be executed transactionally, it won't use the transaction inside the handleAsync method. Because transaction to propagate inside that method, they should be executed on same thread

Upvotes: 7

Related Questions