我可以在持久函数中使用foreach循环吗?

时间:2019-07-17 06:50:43

标签: azure-functions azure-durable-functions

我已经写了我的第一个持久函数,我想知道在协调器函数中使用foreach循环是否可行/好的做法?

业务流程中的第一个活动返回项目ID的列表,我想遍历该列表并使用子业务流程为每个项目ID执行一系列活动。

我已经创建了一个测试函数,它似乎可以工作。我观察到的唯一行为是,每次协调器重播时,当它到达foreach循环时,它会遍历整个列表,直到到达当前项目,然后执行活动。

任何建议/意见将不胜感激。

谢谢

3 个答案:

答案 0 :(得分:1)

只要业务流程中的代码是确定性的,就可以了。 docs中有关代码约束的更多信息。

您提到使用活动功能检索这些ID。只要您使用相同的参数调用函数/子流程,就可以了,因为在重播期间,持久函数会识别该函数之前已被调用过,并将返回持久化的输出(因此不会重新执行同一函数)。

答案 1 :(得分:0)

以您的示例为例,这是非常标准的扇出/扇入情况。您可以在活动上并行运行循环,但请确保异步进行循环。您可以在此处找到用例和示例。

https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-concepts#fan-in-out

基于评论

这正是Orchestrater应该如何工作的。编排正在使用事件源模式。当Orchestrator安排活动时,它将进入睡眠状态,完成活动后将被唤醒。每次协调器唤醒时,它将始终从头开始重播,并检查执行历史记录以查看其是否已经完成了给定的活动并继续前进。因此,在循环的情况下,它将安排所有活动并进入睡眠状态,当唤醒时,它将从开始重播以查看其是否已完成任务。我强烈建议您观看来自Microsoft的Jeff hollan的以下剪辑,我相信之后您将有一个非常清晰的主意。

How Orchestration works

答案 2 :(得分:0)

在 Durable 函数中管理 foreach 的一个关键概念,无论是函数链还是扇入/扇出,都是从 Activity 返回要迭代的数据 并且每个数据项的处理也在 Activity 的上下文中执行。

<块引用>

此模式将确保您的逻辑是确定性的,不要依赖 NonDeterministicOrchestrationException 作为您的逻辑确定性证明,当重放操作发送与预期不同的输入时通常会引发这种情况,并且可能不会直接或最初通知您非确定性逻辑。

对数据库或外部服务或其他 http 端点的任何调用都应被视为非确定性的,因此将类似这样的代码封装在 Activity 中。这样,当 Orchestrator 重放时,它将从底层存储检索先前完成的对该活动的调用的结果。

<块引用>
  1. 如果活动中的逻辑在持久生命周期内仅评估一次,这有助于提高性能。
  2. 如果在重放尝试期间基础提供程序可能暂时不可用,这也将保护您免受可能发生的瞬时错误。

在下面的简单示例中,我们有一个需要在许多设施上执行的翻转功能,我们可以使用扇出来同时或按顺序为每个设施执行单独的任务: >

[FunctionName("RolloverBot")]
public static async Task<bool> RolloverBot(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    // example of how to get data to iterate against in a determinist paradigm
    var facilityIds = await context.CallActivityAsync<int[]>(nameof(GetAllFacilities), null);

    #region Fan-Out
    var tasks = new List<Task>();
    foreach (var facilityId in facilityIds)
    {
        tasks.Add(context.CallActivityAsync(nameof(RolloverFacility), facilityId));
    }
    
    // Fan back in ;)
    await Task.WhenAll(tasks);
    #endregion Fan-Out
  
    #region Chaining / Iterating Sequentially

    foreach (var facilityId in facilityIds)
    {
        await context.CallActivityAsync(nameof(RolloverFacility), facilityId);
    }

    #endregion Chaining / Iterating Sequentially

    return true;
}

/// <summary>
/// Return a list of all FacilityIds to operate on
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[FunctionName("GetAllFacilities")]
public static async Task<int[]> GetAllFacilities([ActivityTrigger] IDurableActivityContext context)
{
    var db = await Globals.GetDataContext();
    var data = await db.Facilities.AddQueryOption("$select", "Id").ExecuteAsync();
    return data.Where(x => x.Id.HasValue).Distinct().Select(x => x.Id.Value).ToArray();
}

[FunctionName("RolloverFacility")]
public static async Task<bool> RolloverFacility(
    [ActivityTrigger] IDurableActivityContext context)
{
    int facilityId = context.GetInput<int>();
    bool result = false;

    ... insert rollover logic here

    result = true;
    return result;
}

这样,即使您的 Activity 逻辑使用 System.RandomGuid.CreateNewDateTimeOffset.Now 来确定要返回的 facilityIds,持久函数本身仍然被认为是确定性并且会正确重放。

作为一项规则,如果您的活动逻辑与时间相关,我仍然建议将 IDurableOrchestrationContext.CurrentUtcDateTime编排功能传递到活动,因为它使逻辑更加明显,Orchestrator 是实际上控制任务,而不是相反,由于CallActivityAsync调度和它的实际执行之间的函数实现,也可能存在微小的滞后时间。 em>.