1

Using jhipster on spring boot 1.5.4, I'm having a hard time getting background tasks to execute asynchronously; they appear to be running synchronously using a different taskExecutor and thread pool than the one I've configured.

All this happens in a service, which for bevity, is defined like so:

@Service
@Transactional
public class AppService {
    @Scheduled(fixedDelay = 3000)
    public void consumeData() {
        // connect to a subscriber and push data to the workerBee
        for(Tuple data : this.getTuples()) {
            workerBee(data);
        }
    }

    @Timed
    @Async
    public void workerBee(Tuple data) throws Exception {
        // ... do something that takes 300ms ....
        Thread.sleep(300);
    }
}

Arguably a service isn't the perfect place for this work, but for demonstration purposes, it fits.

(also as an aside, it apears @Timed isn't working, but I read somewhere that @Timed doesn't work when called internally within the service)

Relevant section of application.yml:

jhipster:
    async:
        core-pool-size: 8
        max-pool-size: 64
        queue-capacity: 10000

Using the default, generated AsyncConfiguration.java, which looks like this:

@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
    log.debug("Creating Async Task Executor");
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
    executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
    executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
    executor.setThreadNamePrefix("app-Executor-");
    return new ExceptionHandlingAsyncTaskExecutor(executor);
}

I have verified that the taskExecutor bean is getting created and is being used by liquibase.

When I connect visualvm I see all the work happening in pool-2-thread-1, which must be some kind of default and it's obvious that the work is happening synchronously, and not asynchronously.

Things I've tried:

  • Specifying the executor in the @Async annotation like @Async("taskExecutor")
  • Verifying configuration of the taskExecutor with 8 threads in the core-pool-size.
  • Verifying that the application has the @EnableAsync annotation (it does by default).

3 Answers 3

1

One alternative is changing the @Bean getAsyncExecutor to this:

@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor () {
    log.debug("Creating Async Task Executor");
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
    executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
    executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
    executor.setThreadNamePrefix("app-Executor-");
    return executor;
}
Sign up to request clarification or add additional context in comments.

2 Comments

I'm not seeing what you did here other than remove the ExceptionHandlingAsyncTaskExecutor wrapper round the executor. Why would/should that help?
Ignore the answer I just figure out that you are using jhipster
1

Looks like I'm not following the rules laid out here: http://www.baeldung.com/spring-async. Most notably self-invocation:

@Async has two limitations:

it must be applied to public methods only
self-invocation – calling the async method from within the same class – won’t work

Comments

1

Alternative 2: call the async method in the same class using CompletableFuture

Here is another approach if you need to use an async method and call it in the same class using CompletableFuture and injecting the Executor generated from AsyncConfiguration

@Service
public class MyAsyncProcess {

    private final Logger log = LoggerFactory.getLogger(MyAsyncProcess.class);

    @Autowired
    Executor executor;


    @Scheduled(cron = "*/8 * * * * *")
    public void consumeData() {

        IntStream.range(0,20).forEach( (s) ->
            CompletableFuture.supplyAsync( () -> { return workerBeeCompletableFuture(String.valueOf(s)); } , executor));
    }


    public  CompletableFuture<String> workerBeeCompletableFuture(String data)  {

        log.debug("workerBeeCompletableFuture: Iteration number: " + data + " Thread: " + Thread.currentThread().getName());

        try { Thread.sleep(2000); }
        catch (InterruptedException e) { e.printStackTrace(); }

        return CompletableFuture.completedFuture("finished");

    }

Alternative 1 using @Async

I finally get what is causing this behavior, actually the @Scheduler is calling to the workerBee as a local method and not as @Async method.

In order to make @Async work just create a new @Service for the @Schedule called MySchedulerService and @Autowired the AppService into the MySchedulerService. Also remove the @Schedule from the AppService class.

It should be like this:

@Service
public class MyAsyncProcess {

    private final Logger log = LoggerFactory.getLogger(MyAsyncProcess.class);

    @Async
    public  void workerBeeAsync(String data)  {
        // ... do something that takes 300ms ....
        try {
            log.debug("Iteration number: " + data + " Thread: " + Thread.currentThread().getName());

            Thread.sleep(2000);
            log.debug("finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

And the Schedule Service

@Service
public class MySchedule {

    @Autowired
    MyAsyncProcess myAsyncProcess;

    @Scheduled(cron = "*/8 * * * * *")
    public void consumeData() {

        IntStream.range(0,20).forEach(s ->
            myAsyncProcess.workerBeeAsync(String.valueOf(s)));

    }

}

In the application.yml I use the following values:

jhipster:
    async:
        core-pool-size: 50
        max-pool-size: 100
        queue-capacity: 10000

The @Async will autodetect the Executor configured in AsyncConfigurer class.

Hope it help you.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.