我有一个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 阶段的处理程序将触发两次,我最终尝试执行清理两次。
我可以在我自己的代码中解决这个问题,但我期待状态机能够管理它。我做错了什么,或者我只是需要自己处理争用?
答案 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; }
}
方法将其捕获到状态实例中。