2

I have seen a few similar questions on here, but none of their solutions work. My 2 async calls run synchronously.

HomeController.java

@Controller
@EnableOAuth2Sso
public class HomeController {

@Autowired
private ProjectService projectService;

@RequestMapping("/")
public String home(Model model) {
    Future<List<Project>> latest = projectService.getLatest();
    Future<List<Project>> popular = projectService.getPopular();

    try {
        while (!(latest.isDone() && popular.isDone())) {
            Thread.sleep(10); //10-millisecond pause between each check
        }

        model.addAttribute("latest", latest.get());
        model.addAttribute("popular", popular.get());
    } catch(Exception e) {
        System.out.println("ERROR in async thread " + e.getMessage());
    }

    return "home";
 }
}

ProjectService.java

@Service
public class ProjectService {

@Autowired
private MessageSendingOperations<String> messagingTemplate;

@Autowired
private ProjectRepository projectRepository;

@Async("taskExecutor")
public Future<List<Project>> getLatest() {
    return new AsyncResult<List<Project>>(this.projectRepository.getLatest());
}

@Async("taskExecutor")
public Future<List<Project>> getPopular() {
    return new AsyncResult<List<Project>>(this.projectRepository.getPopular());
}

ProjectRepository.java

@Component
public class ProjectRepository {

public List<Project> getLatest() {
    return this.generateData(); // 10 seconds to generate random data
}

public List<Project> getPopular() {
    return this.generateData();  // 10 seconds to generate random data
}

 public List<Project> generateData() {
    try { Thread.sleep(10000); } catch(Exception e) {}

    ArrayList<Project> projects = new ArrayList<Project>();
    ArrayList<Repository> repositories = new ArrayList<Repository>();

    repositories.add(
            new Repository("repo-name1", "repo-url-1")
    );
    repositories.add(
            new Repository("repo-name2", "repo-url-2")
    );

    projects.add(
            new Project("title10", "description10")
                    .setViews(new Random().nextInt(10000))
                    .setRepositories(repositories)
    );
    projects.add(new Project("title20", "description20").setViews(new Random().nextInt(1000)));

    projects.add(new Project("title", UUID.randomUUID().toString()).setViews(new Random().nextInt(100)));

    return projects;
}
}

AsyncConfig.java

@Configuration
@EnableAsync
public class AsyncConfig {

@Value("${pool.size:10}")
private int poolSize;;

@Value("${queue.capacity:10}")
private int queueCapacity;

@Bean(name="taskExecutor")
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setMaxPoolSize(this.poolSize);
    taskExecutor.setQueueCapacity(this.queueCapacity);
    taskExecutor.afterPropertiesSet();
    return new ThreadPoolTaskExecutor();
}
}

Both Service calls from the controller should take approximately 10 seconds if running async, but it still takes 20 seconds - what am i missing?

PS. Ignore the simplicity & stupidity, no interfaces etc, I removed as much as possible to try and isolate the issue. I originally followed the official docs https://spring.io/guides/gs/async-method/

Code on GitHub: https://github.com/DashboardHub/PipelineDashboard/tree/feature/homepage

6
  • Can you try to substitute this.generateData() with a Thread.sleep(10000) and tell us what happens? if generateData() is cpu bound and the threads get assigned to the same core the total time may be 20 seconds even if the two task runs async. Commented Oct 16, 2015 at 6:33
  • I have added my generateData() method which contains the sleep. Any other info you need? Commented Oct 16, 2015 at 6:37
  • can you please push that code to github? So it will be easier to play around. IMO it will be much more readable if you use CompletableFuture Commented Oct 16, 2015 at 7:13
  • I will push to GitHub, you are right, it will be easier! :) Commented Oct 16, 2015 at 8:06
  • Url add to original post, but here it is too, thanks github.com/DashboardHub/PipelineDashboard/tree/feature/homepage Commented Oct 16, 2015 at 8:16

1 Answer 1

1

Here is a simple and working test case based on your code (it's adapted for running from command line):

package test.springAsync;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@ComponentScan(basePackages = "test.springAsync")
@EnableAsync
public class AsyncConfig {

private int poolSize=10;

private int queueCapacity=10;

    @Bean(name = "taskExecutor1")
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(this.poolSize);
        taskExecutor.setQueueCapacity(this.queueCapacity);
        taskExecutor.afterPropertiesSet();
        taskExecutor.setThreadNamePrefix("testExecutor");
        return taskExecutor;
    }
}

Project class

package test.springAsync;

public class Project {
    private String name;

    public Project(String title, String description) {

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Project setViews(int value) {
        return this;
    }

}

ProjectRepository class:

package test.springAsync;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class ProjectRepository {
    private Logger logger=LoggerFactory.getLogger(ProjectRepository.class.getName());

    public List<Project> getLatest() {
        return this.generateData(); // 10 seconds to generate random data
    }

    public List<Project> getPopular() {
        return this.generateData(); // 10 seconds to generate random data
    }

    public List<Project> generateData() {
        logger.debug("generateData start");
        try {
            Thread.sleep(10000);
        } catch (Exception e) {
        }

        ArrayList<Project> projects = new ArrayList<Project>();


        projects.add(new Project("title10", "description10").setViews(new Random().nextInt(10000)));
        projects.add(new Project("title20", "description20").setViews(new Random().nextInt(1000)));

        projects.add(new Project("title", UUID.randomUUID().toString()).setViews(new Random().nextInt(100)));
        logger.debug("generateData end");
        return projects;
    }
}

ProjectService class:

package test.springAsync;

import java.util.List;
import java.util.concurrent.Future;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

@Service
public class ProjectService {


    @Autowired
    private ProjectRepository projectRepository;

    @Async("taskExecutor1")
    public Future<List<Project>> getLatest() {
        return new AsyncResult<List<Project>>(this.projectRepository.getLatest());
    }

    @Async("taskExecutor1")
    public Future<List<Project>> getPopular() {
        return new AsyncResult<List<Project>>(this.projectRepository.getPopular());
    }
}

TestAsyncBean that I sues as ana alternative for your controller:

package test.springAsync;

import java.util.List;
import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TestAsyncBean {
    private Logger logger=LoggerFactory.getLogger(TestAsyncBean.class.getName());
    @Autowired
    private ProjectService projectService;
    public void home() {
        logger.debug("start Home");
        Future<List<Project>> latest = projectService.getLatest();
        Future<List<Project>> popular = projectService.getPopular();

        try {
            while (!(latest.isDone() && popular.isDone())) {
                Thread.sleep(1000); //10-millisecond pause between each check
                logger.debug("waiting for AsyncMethods");
            }
            logger.debug("AsyncMethods did the job");
        } catch(Exception e) {
            System.out.println("ERROR in async thread " + e.getMessage());
        }

     }
}

TestMain the class I used for keeping everything together:

package test.springAsync;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestMain {

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AsyncConfig.class);
        TestAsyncBean myService = ctx.getBean(TestAsyncBean.class);
        myService.home();

    }

}

And finally here is the output of main:

2015-10-16 09:16:19,231 [main] DEBUG test.springAsync.TestAsyncBean - start Home 
2015-10-16 09:16:19,244 [testExecutor1] DEBUG test.springAsync.ProjectRepository - generateData start 
2015-10-16 09:16:19,244 [testExecutor2] DEBUG test.springAsync.ProjectRepository - generateData start 
2015-10-16 09:16:20,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:21,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:22,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:23,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:24,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:25,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:26,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:27,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:28,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:29,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:29,298 [testExecutor2] DEBUG test.springAsync.ProjectRepository - generateData end 
2015-10-16 09:16:29,298 [testExecutor1] DEBUG test.springAsync.ProjectRepository - generateData end 
2015-10-16 09:16:30,236 [main] DEBUG test.springAsync.TestAsyncBean - waiting for AsyncMethods 
2015-10-16 09:16:30,236 [main] DEBUG test.springAsync.TestAsyncBean - AsyncMethods did the job 

As you can see frrm the log two threads started and ran in parallel terminating after about 11 seconds. I suggest you to put some logs in your code and to create a sample runnable from the command line in order to diagnose easily what is the problem

Sign up to request clarification or add additional context in comments.

5 Comments

Yes, sorry, that was a copy / paste error from me, as i was partly through changing something. I checked and I am returning taskExecutor
@EddieJaoude I rewrote my answer with a working example runnable from a command line or inside the ide
Thanks! I will have a play :)
I tried to follow the same approach in my spring boot project. But it dint work. Do I need to create separate class for Async methods?
I've never used Spring boot so I'm not sure,but my example relies only on Spring and it should work even when it's "embedded" in spring boot. Did you try my example or did you create some custom code?

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.