处理多个事件的状态转换

时间:2016-04-18 00:32:29

标签: masstransit automatonymous

我有一个MassTransitStateMachine来编排一个涉及创建多个事件的过程。

完成所有事件后,我希望状态转换为“清理”阶段。

这是相关的状态声明和过滤功能:

        During(ImportingData,
            When(DataImported)
                // When we get a data imported event, mark this source as done. 
                .Then(MarkImportCompletedForLocation),

            When(DataImported, IsAllDataImported)
                // Once all are done, we can transition to cleaning up...
                .Then(CleanUpSources)
                .TransitionTo(CleaningUp)
        );


    ...snip...


    private static bool IsAllDataImported(EventContext<DataImportSagaState, DataImportMappingCompletedEvent> ctx)
    {
        return ctx.Instance.Locations.Values.All(x => x);
    }

因此,当状态是 ImportingData 时,我希望收到多个 DataImported 事件。每个事件都将其位置标记为已完成,以便 IsAllDataImported 方法可以确定是否应该转换到下一个状态。

但是,如果最后两个 DataImported 事件同时到达,则转换到 CleaningUp 阶段的处理程序将触发两次,我最终尝试执行清理两次。

可以在我自己的代码中解决这个问题,但我期待状态机能够管理它。我做错了什么,或者我只是需要自己处理争用?

2 个答案:

答案 0 :(得分:4)

Chris提出的解决方案在我的情况下不起作用,因为我有多个相同类型的事件到达。我只有在所有这些事件都到来时才需要转换。 CompositeEvent 构造不适用于此用例。

我的解决方案是在 MarkImportCompletedForLocation 方法中引发新的 AllDataImported 事件。此方法现在处理以线程安全方式确定是否所有子导入都已完成。

所以我的状态机定义是:

            During(ImportingData,
            When(DataImported)
                // When we get a data imported event, mark the URI in the locations list as done. 
                .Then(MarkImportCompletedForLocation),

            When(AllDataImported)
                // Once all are done, we can transition to cleaning up...
                .TransitionTo(CleaningUp)
                .Then(CleanUpSources)
        );

不再需要 IsAllDataImported 方法作为过滤器。

saga州有一个Locations属性:

public Dictionary<Uri, bool> Locations { get; set; }

MarkImportCompletedForLocation方法定义如下:

    private void MarkImportCompletedForLocation(BehaviorContext<DataImportSagaState, DataImportedEvent> ctx)
    {
        lock (ctx.Instance.Locations)
        {
            ctx.Instance.Locations[ctx.Data.ImportSource] = true;
            if (ctx.Instance.Locations.Values.All(x => x))
            {
                var allDataImported = new AllDataImportedEvent {CorrelationId = ctx.Instance.CorrelationId};
                this.CreateEventLift(AllDataImported).Raise(ctx.Instance, allDataImported);
            }
        }
    }

(我刚刚写了这篇文章,以便了解一般流程如何工作;我认识到MarkImportCompletedForLocation方法需要通过验证字典中是否存在密钥来更加防御。)

答案 1 :(得分:1)

您可以使用复合事件将多个事件累积到后续事件中,该事件在依赖事件触发时触发。这是使用:

定义的
foreach ($item in q1 ){
    //load again from location
    q2 = "SELECT date, starttime, endtime FROM Table WHERE location = ". $location .";"

    //print date only once
    echo "Date(s): ".date column;

    //loop the time
    foreach ($item in q2){
        //put the logic to create or by yourself
        echo "Time(s): ". $item->starttime ."-". $item->endtime;
    }
}

然后,在状态机状态实例中:

CompositeEvent(() => AllDataImported, x => x.ImportStatus, DataImported, MoreDataImported);

During(ImportingData,
    When(DataImported)
        .Then(context => { do something with data }),
    When(MoreDataImported)
        .Then(context => { do smoething with more data}),
    When(AllDataImported)
        .Then(context => { okay, have all data now}));

这应该解决你想要解决的问题,所以试一试。请注意,事件顺序无关紧要,它们可以按任何顺序到达,因为已收到事件的状态位于实例的ImportStatus属性中。

不会保存单个事件的数据,因此您需要使用class DataImportSagaState : SagaStateMachineInstance { public int ImportStatus { get; set; } } 方法将其捕获到状态实例中。