我有三个谷物(A,B和C)在管道中做不同的工作。 GrainA将结果传递给grainB,grainB将结果传递给grainC。我想保证连续的晶粒之间的顺序消息发送,这可以通过下面的方式实现。
// client code
foreach(var i in list)
{
await grainA.job(i);
}
//grain A code
async Task job(var i)
{
DoSomeWorkA(i);
await grainB.job(i);
}
//grain B code
async Task job(var i)
{
DoSomeWorkB(i);
await grainC.job(i);
}
//grain C code
async Task job(var i)
{
DoSomeWorkC(i);
Console.WriteLine(i);
}
但是,此代码的问题是没有流水线。仅当当前对象遍历所有grainB和grainC时(由于await语句),grainA才被赋予净对象。获得流水线的一种方法是不使用await并直接一个接一个地发送对象。但是,这会导致无序交付,如this post中所述。
我想让执行完全流水线化,这意味着当grainB接收到来自grainA的结果时,grainA继续执行下一个工作。但是,当我发送一些控制消息时,消息排序也很重要。在奥尔良该怎么做?
答案 0 :(得分:3)
为了使高度并发的系统易于编程和推理,奥尔良将每个粒度的并行性和并发性(计算和状态的单位)限制为1。这意味着最多将同时并以大多数线程将在任何时间执行给定的谷物激活代码。
这大大简化了开发人员需要专注的工作,因为不再需要诸如锁之类的线程同步原语。
但是,该行为是可配置的。如果您不希望这种默认的“安全”行为,则可以在谷物的界面上将谷物标记为[Reentrant]
或将单个方法标记为[AlwaysInterleave]
。
这允许同时处理许多消息。在grain方法的每个await
点处,执行都返回给调度程序,并且调度程序可以开始处理另一条消息。调度程序仍确保每次激活谷物都具有单线程性,因此仍不需要锁定,但允许消息在那些await
点进行交织(即,协作多任务)。这意味着开发人员现在需要考虑在这些await
点之间的其他请求可能如何改变内部状态。
有关更多信息,请参见Reentrancy page in the Orleans documentation。
为了提高并行度(针对与CPU绑定的任务),开发人员可以通过Task.Run
或类似方式使用Stateless Workers或External Tasks。
还请注意,无论并发配置如何,从谷物激活A发送到谷物激活B的消息将始终按顺序发送。同样,对于从B发送到A的结果。请注意,一旦提高并发性,仍然很难对消息的顺序进行推理,但是,由于对B的第二次调用可能在第一次调用之前完成,因此其结果将更早发送,也。这意味着结果可能看起来是乱序接收的。我想这就是您所期望的。