La méthode Spring Boot @Async dans le contrôleur s'exécute de manière synchrone

mon application [basic] Spring Boot accepte une requête du navigateur, envoyée via jQuery.get() et est censé recevoir immédiatement une réponse, comme " votre demande a été mise en attente". Pour ce faire, j'ai écrit un contrôleur:

@Controller
public class DoSomeWorkController {

  @Autowired
  private final DoWorkService workService;

  @RequestMapping("/doSomeWork")
  @ResponseBody
  public String doSomeWork() {

    workService.doWork(); // time consuming operation
    return "Your request has been queued.";
  }
}

DoWorkServiceImpl la classe implémente DoWorkService interface et est vraiment simple. Il dispose d'une méthode unique pour effectuer une tâche fastidieuse. Je n'ai pas besoin d'un retour de cet appel de service, car un e-mail sera livré à la fin du travail, à la fois pour les scénarios d'échec ou de succès. Ainsi, cela ressemblerait effectivement à:

@Service
public class DoWorkServiceImpl implements DoWorkService {

  @Async("workExecutor")
  @Override
  public void doWork() {

    try {
        Thread.sleep(10 * 1000);
        System.out.println("completed work, sent email");
    }
    catch (InterruptedException ie) {
        System.err.println(ie.getMessage());
    }
  }
}

je pensais que cela fonctionnerait, mais la requête Ajax du navigateur a attendu 10 secondes avant de retourner la réponse. Ainsi, la méthode mapped controller appelle la méthode interne annotée avec @Async en mode synchrone, il semblerait. Dans une application Spring traditionnelle, j'ajoute typiquement ceci à la configuration XML:

<task:annotation-driven />
<task:executor id="workExecutor" pool-size="1" queue-capacity="0" rejection-policy="DISCARD" />

alors j'ai pensé écrire le l'équivalent de ceci dans la classe d'application principale aiderait:

@SpringBootApplication
@EnableAsync
public class Application {

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

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

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

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

cela n'a pas changé le comportement. La réponse Ajax arrive toujours après 10 Secondes d'envoi de la requête. Ce qui me manque?

L'application de démarrage à ressort peut être télécharger ici. Avec Maven installé, le projet peut être exécuté avec la commande simple:

mvn clean spring-boot:run

Remarque: le problème a été résolu grâce à la réponse fournie par @Dave Syer ci-dessous, qui a fait remarquer que je manquais @EnableAsync dans mon application, même si j'avais la ligne dans l'extrait de code ci-dessus.

19
demandé sur Web User 2015-03-26 19:35:21

4 réponses

Vous appelez l' @Async méthode à partir d'une autre méthode de la même classe. Sauf si vous activez le mode proxy D'AspectJ pour @EnableAsync (et de fournir un tisserand, bien sûr) qui ne fonctionne pas (google "proxy auto-invocation"). La meilleure solution est de mettre le @Async méthode dans un autre @Bean.

35
répondu Dave Syer 2015-03-26 17:07:44

Pour tous ceux qui sont toujours à la recherche de toutes les étapes de @Asnyc expliqué d'une manière simple, voici la réponse:

Voici un exemple simple avec @Asynchrone. Suivez ces étapes pour que @Async fonctionne dans votre application de démarrage:

Étape 1: Ajouter @ EnableAsync annotation et Ajouter Taskexecutor Bean à la classe D'Application.

Exemple:

@SpringBootApplication
@EnableAsync
public class AsynchronousSpringBootApplication {

    private static final Logger logger = LoggerFactory.getLogger(AsynchronousSpringBootApplication.class);

    @Bean(name="processExecutor")
    public TaskExecutor workExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("Async-");
        threadPoolTaskExecutor.setCorePoolSize(3);
        threadPoolTaskExecutor.setMaxPoolSize(3);
        threadPoolTaskExecutor.setQueueCapacity(600);
        threadPoolTaskExecutor.afterPropertiesSet();
        logger.info("ThreadPoolTaskExecutor set");
        return threadPoolTaskExecutor;
    }

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

Étape 2: Ajouter une méthode qui exécute un processus asynchrone

@Service
public class ProcessServiceImpl implements ProcessService {

    private static final Logger logger = LoggerFactory.getLogger(ProcessServiceImpl.class);

    @Async("processExecutor")
    @Override
    public void process() {
        logger.info("Received request to process in ProcessServiceImpl.process()");
        try {
            Thread.sleep(15 * 1000);
            logger.info("Processing complete");
        }
        catch (InterruptedException ie) {
            logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
        }
    }
}

Étape 3: Ajouter une API dans le contrôleur pour exécuter le traitement asynchrone

@Autowired
private ProcessService processService;

@RequestMapping(value = "ping/async", method = RequestMethod.GET)
    public ResponseEntity<Map<String, String>> async() {
        processService.process();
        Map<String, String> response = new HashMap<>();
        response.put("message", "Request is under process");
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

j'ai aussi écrit un blog et une application de travail sur GitHub avec ces étapes. Veuillez vérifier: http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html

8
répondu Aj Tech Developer 2017-08-11 16:06:37

j'ai eu un problème similaire et j'ai eu les annotations @Async et @EnableAsync dans les haricots corrects et toujours la méthode a été l'exécution synchrone. Après que j'ai vérifié les logs il y avait un avertissement disant que j'avais plus d'un haricot de type ThreadPoolTaskExecutor et aucun d'eux appelé taskExecutor...

@Bean(name="taskExecutor")
public ThreadPoolTaskExecutor defaultTaskExecutor() {
     ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
     //Thread pool configuration
     //...
     return pool;
}

Voir http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html pour la configuration disponible pour le pool de threads.

3
répondu Carlos Andres 2017-01-20 15:55:04

Suivez les trois étapes suivantes :

1 Step : Utilisez @EnableAsync avec @configuration ou @SpringBootApplication

@EnableAsync Public class Application {

2 Étape :

/**
 * THIS FOR ASYNCRONOUS PROCESS/METHOD
 * @return
 */
@Bean
public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(5);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("Anycronous Process-");
    executor.initialize();
    return executor;
}

Étape 3: @Async sur la methode

T

0
répondu Sanjeet Shrivastava 2018-06-01 21:33:44