Разветвление и объединение: проблема с параллельным выполнением двух областей в spring state machine

#java #fork-join #spring-statemachine

#java #разветвление-объединение #spring-statemachine

Вопрос:

Я попытался использовать структуру «fork/join» для параллельного выполнения конечного автомата в областях состояния «S2». Конфигурация конечного автомата была основана на модели uml. И у меня проблема с параллельной работой этих двух областей. Сначала конечный автомат переходит из состояний S20 в S23, а затем передает состояния с S30 на S33.

В чем может быть проблема? Заранее благодарю вас за вашу помощь.

Это конфигурация моего конечного автомата:

 @Slf4j
@Configuration
@EnableStateMachine
public class StateMachineConfig extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
        model
                .withModel()
                .factory(modelFactory());
    }

    @Bean
    public StateMachineModelFactory<String, String> modelFactory() {
        Resource model = new ClassPathResource("/uml/simple-forkjoin.uml");
        return new UmlStateMachineModelFactory(model);
    }


     @Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
     config
     .withConfiguration()
     .listener(new StateMachineListener())
     .autoStartup(true);
     }
}
  

Пример класса, представляющего состояние:

 @Slf4j
@WithStateMachine
public class S22 {


    @OnTransition(target = "S22")
    public void onTransition(StateContext<String, String> stateContext) {
        log.info(Colours.ANSI_BLUE   "target = "S22", getState().getId(): "
                  stateContext.getStateMachine().getState().getId()
                  Colours.ANSI_RESET);
        Sleep.sleep(1000);
    }

    @OnStateChanged(target = "S22")
    public void onStateChanged(StateContext<String, String> stateContext) {
        log.debug(Colours.B_HI_ANSI_GREEN   "S22 EXPECTS_ORDERS stateChanged :: Current state ID :: {}",
                stateContext.getStateMachine().getState().getId()   Colours.ANSI_RESET);
        Sleep.sleep(1000);
    }
}
  

журналы:

 2019-03-21 12:25:01.746  INFO 7648 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms
2019-03-21 12:25:01.780  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S0
2019-03-21 12:25:01.788  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S0          : target = "S0, getState().getId(): SI
2019-03-21 12:25:01.791 DEBUG 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S0          : S0 EXPECTS_ORDERS stateChanged :: Current state ID :: S0
2019-03-21 12:25:01.791  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : state changed from: SI to S0
2019-03-21 12:25:01.791  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S1
2019-03-21 12:25:01.792  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S1          : target = "S1", getState().getId(): S0
2019-03-21 12:25:01.794  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S21
2019-03-21 12:25:01.794  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S21         : target = "S21", getState().getId(): S2
2019-03-21 12:25:02.795  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S22
2019-03-21 12:25:02.797  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S22         : target = "S22", getState().getId(): S2
2019-03-21 12:25:03.799  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S23
2019-03-21 12:25:03.801  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S23         : target = "S23", getState().getId(): S2
2019-03-21 12:25:04.803  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : state changed from: S22 to S23
2019-03-21 12:25:04.803  INFO 7648 --- [nio-8080-exec-2] o.s.s.support.LifecycleObjectSupport     : started org.springframework.statemachine.support.DefaultStateMachineExecutor@76b2b847
2019-03-21 12:25:04.803  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : State machine started :: S2
2019-03-21 12:25:04.804  INFO 7648 --- [nio-8080-exec-2] o.s.s.support.LifecycleObjectSupport     : started S23 S22 S20 S21  / S23 / uuid=51391415-d1f5-440f-9c8e-677c00bcdd50 / id=null
2019-03-21 12:25:04.805 DEBUG 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S22         : S22 EXPECTS_ORDERS stateChanged :: Current state ID :: S2
2019-03-21 12:25:05.806  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : state changed from: S21 to S22
2019-03-21 12:25:05.806 DEBUG 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S21         : S21 EXPECTS_ORDERS stateChanged :: Current state ID :: S2
2019-03-21 12:25:06.807  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : state changed from: S20 to S21
2019-03-21 12:25:06.808 DEBUG 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S20         : S20 EXPECTS_ORDERS stateChanged :: Current state ID :: S2
2019-03-21 12:25:07.809  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : state changed from: null to S20
2019-03-21 12:25:07.809  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S31
2019-03-21 12:25:07.810  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S31         : target = "S31", getState().getId(): S2
2019-03-21 12:25:08.211  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S32
2019-03-21 12:25:08.212  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S32         : target = "S32", getState().getId(): S2
2019-03-21 12:25:08.613  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S33
2019-03-21 12:25:08.614  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S33         : target = "S33", getState().getId(): S2
2019-03-21 12:25:09.015  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S3
2019-03-21 12:25:09.016  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S3          : target = "S3", getState().getId(): S2
2019-03-21 12:25:09.016  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : transition started! target state: S3
2019-03-21 12:25:09.017  INFO 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S3          : target = "S3", getState().getId(): S2
2019-03-21 12:25:09.018  INFO 7648 --- [nio-8080-exec-2] o.s.s.support.LifecycleObjectSupport     : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@76b2b847
2019-03-21 12:25:09.018  INFO 7648 --- [nio-8080-exec-2] o.s.s.support.LifecycleObjectSupport     : stopped S23 S22 S20 S21  /  / uuid=51391415-d1f5-440f-9c8e-677c00bcdd50 / id=null
2019-03-21 12:25:09.019 DEBUG 7648 --- [nio-8080-exec-2] com.fj.forkJointest.forkJoin.S4          : S4 EXPECTS_ORDERS stateChanged :: Current state ID :: S4
2019-03-21 12:25:09.020  INFO 7648 --- [nio-8080-exec-2] c.f.f.config.StateMachineListener        : state changed from: S2 to S4
  

Это мой проект на github

Ответ №1:

Spring State Machine использует TaskExecutor для выполнения регионов и по умолчанию это синхронно. Для достижения асинхронного выполнения вам необходимо переопределить исполнителя задачи по умолчанию.

Вы можете сделать это несколькими способами:

Используя StateMachineConfigurationConfigurer :

 @Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
    config
        .withConfiguration()
            //other configs
            .taskExecutor(myAsyncTaskExecutor())
}

public TaskExecutor myAsyncTaskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(5);
    return taskExecutor;
}
  

Или просто объявление компонента, который будет автоматически получен из SM:

 @Bean(name = StateMachineSystemConstants.TASK_EXECUTOR_BEAN_NAME)
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(5);
    return taskExecutor;
}
  

Комментарии:

1. Спасибо, Ованессиан, ваш метод сработал, объявив Bean. Но только тогда, когда я изменил структуру Fork в UML. Вместо двух стрелок, указывающих на две машины ( UML не работает ) Я использовал одну ( работающую на UML ), указывающую состояние S2, в котором они находятся. В противном случае все выполняется в одном потоке (eTaskExecutor-3), за исключением состояния SI, инициирующего главный конечный автомат (eTaskExecutor-1). Ваш первый вариант вызвал ошибку: … Ошибка при создании компонента с именем ‘StateMachine’: … ThreadPoolTaskExecutor не инициализирован

2. В чем может заключаться проблема?

3. @Brutus просто следуйте официальному примеру fork / join и адаптируйте соответственно к вашему случаю docs.spring.io/autorepo/docs/spring-statemachine/1.2.x-SNAPSHOT/… Также, если ответ решил вашу проблему, подумайте о том, чтобы принять его, чтобы другие могли видеть, что он решает проблему, описанную в вопросе

4. @Brutus Я пытался изменить UML, как вы упомянули, но у меня это не работает.

Ответ №2:

Исполнитель должен быть определен следующим образом

 public TaskExecutor myAsyncTaskExecutor() {
            ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
            taskExecutor.setCorePoolSize(5);
            taskExecutor.initialize();
            return taskExecutor;
        }
  

Конфигурация конечного автомата

 .taskExecutor(myAsyncTaskExecutor()).taskScheduler(new ConcurrentTaskScheduler())