持久业务流程Durable Orchestrations

Durable Functions 是 Azure Functions 的一个扩展。Durable Functions is an extension of Azure Functions. 可以使用业务流程协调程序函数来协调函数应用中其他持久函数的执行。You can use an orchestrator function to orchestrate the execution of other Durable functions within a function app. 业务流程协调程序函数具有以下特征:Orchestrator functions have the following characteristics:

  • 业务流程协调程序函数使用过程代码来定义函数工作流。Orchestrator functions define function workflows using procedural code. 不需要声明性的架构或设计器。No declarative schemas or designers are needed.
  • 业务流程协调程序函数可以同步和异步调用其他持久函数。Orchestrator functions can call other durable functions synchronously and asynchronously. 被调用函数的输出可以可靠保存到本地变量。Output from called functions can be reliably saved to local variables.
  • 业务流程协调程序函数是持久且可靠的。Orchestrator functions are durable and reliable. 当函数处于“等待”或“生成”状态时,可自动对执行进度设置检查点。Execution progress is automatically checkpointed when the function "awaits" or "yields". 回收进程或重启 VM 时,从来不会丢失本地状态。Local state is never lost when the process recycles or the VM reboots.
  • 业务流程协调程序函数可以长时间运行。Orchestrator functions can be long-running. 业务流程实例的总生存期可以是几秒、几天、几月或者永不结束。The total lifespan of an orchestration instance can be seconds, days, months, or never-ending.

本文概述业务流程协调程序功能,及其如何帮助解决各种应用开发难题。This article gives you an overview of orchestrator functions and how they can help you solve various app development challenges. 如果你不熟悉 Durable Functions 应用中可用的函数类型,请先阅读持久函数类型一文。If you are not already familiar with the types of functions available in a Durable Functions app, read the Durable Function types article first.

业务流程标识Orchestration identity

业务流程的每个实例都有一个实例标识符(也称为“实例 ID”)。Each instance of an orchestration has an instance identifier (also known as an instance ID). 默认情况下,每个实例 ID 都是自动生成的 GUID。By default, each instance ID is an autogenerated GUID. 但是,实例 ID 也可以是用户生成的任何字符串值。However, instance IDs can also be any user-generated string value. 每个业务流程实例 ID 在任务中心内必须唯一。Each orchestration instance ID must be unique within a task hub.

下面是有关实例 ID 的一些规则:The following are some rules about instance IDs:

  • 实例 ID 必须为 1 到 256 个字符。Instance IDs must be between 1 and 256 characters.
  • 实例 ID 不能以 @ 开头。Instance IDs must not start with @.
  • 实例 ID 不能包含 /\#? 字符。Instance IDs must not contain /, \, #, or ? characters.
  • 实例 ID 不能包含控制字符。Instance IDs must not contain control characters.

备注

通常建议尽可能使用自动生成的实例 ID。It is generally recommended to use autogenerated instance IDs whenever possible. 用户生成的实例 ID 适用于业务流程实例与某些外部应用程序特定的实体(例如采购订单或文档)之间存在一对一映射的情况。User-generated instance IDs are intended for scenarios where there is a one-to-one mapping between an orchestration instance and some external application-specific entity, like a purchase order or a document.

业务流程的实例 ID 是大多数实例管理操作的必需参数。An orchestration's instance ID is a required parameter for most instance management operations. 它们对于诊断也很重要,例如,在 Application Insights 中搜索业务流程跟踪数据以进行故障排除或分析。They are also important for diagnostics, such as searching through orchestration tracking data in Application Insights for troubleshooting or analytics purposes. 出于此原因,建议将生成的实例 ID 保存到可在今后方便引用的某个外部位置(例如数据库或应用程序日志中)。For this reason, it is recommended to save generated instance IDs to some external location (for example, a database or in application logs) where they can be easily referenced later.

可靠性Reliability

业务流程协调程序函数使用事件溯源设计模式可靠维护其执行状态。Orchestrator functions reliably maintain their execution state by using the event sourcing design pattern. Durable Task Framework 使用仅限追加的存储来记录函数业务流程执行的一系列完整操作,而不是直接存储业务流程的当前状态。Instead of directly storing the current state of an orchestration, the Durable Task Framework uses an append-only store to record the full series of actions the function orchestration takes. 与“转储”整个运行时状态相比,仅限追加的存储具有很多优点。An append-only store has many benefits compared to "dumping" the full runtime state. 优点包括提高性能、可伸缩性和响应能力。Benefits include increased performance, scalability, and responsiveness. 此外,它还提供事务数据的最终一致性,以及完整的审核线索和历史记录。You also get eventual consistency for transactional data and full audit trails and history. 审核线索支持可靠的补偿操作。The audit trails support reliable compensating actions.

Durable Functions 以透明方式使用事件溯源。Durable Functions uses event sourcing transparently. 在幕后,业务流程协调程序函数中的 await (C#) 或 yield (JavaScript) 运算符将对业务流程协调程序线程的控制权让回给 Durable Task Framework 调度程序。Behind the scenes, the await (C#) or yield (JavaScript) operator in an orchestrator function yields control of the orchestrator thread back to the Durable Task Framework dispatcher. 然后,该调度程序向存储提交业务流程协调程序函数计划的任何新操作(如调用一个或多个子函数或计划持久计时器)。The dispatcher then commits any new actions that the orchestrator function scheduled (such as calling one or more child functions or scheduling a durable timer) to storage. 透明的提交操作会追加到业务流程实例的执行历史记录中。The transparent commit action appends to the execution history of the orchestration instance. 历史记录存储在存储表中。The history is stored in a storage table. 然后,提交操作向队列添加消息,以计划实际工作。The commit action then adds messages to a queue to schedule the actual work. 此时,可从内存中卸载业务流程协调程序函数。At this point, the orchestrator function can be unloaded from memory.

如果业务流程函数需要执行其他工作(例如,收到响应消息或持久计时器到期),业务流程协调程序将会唤醒,并从头开始重新执行整个函数,以重新生成本地状态。When an orchestration function is given more work to do (for example, a response message is received or a durable timer expires), the orchestrator wakes up and re-executes the entire function from the start to rebuild the local state. 在重播过程中,如果代码尝试调用函数(或执行任何其他异步工作),Durable Task Framework 会查询当前业务流程的执行历史记录。During the replay, if the code tries to call a function (or do any other async work), the Durable Task Framework consults the execution history of the current orchestration. 如果该扩展发现活动函数已执行并已生成结果,则会回放该函数的结果并且业务流程协调程序代码继续运行。If it finds that the activity function has already executed and yielded a result, it replays that function's result and the orchestrator code continues to run. 在函数代码完成或计划了新的异步工作之前,重放会一直继续。Replay continues until the function code is finished or until it has scheduled new async work.

备注

要使重播模式正常可靠工作,业务流程协调程序函数代码必须是确定性的。In order for the replay pattern to work correctly and reliably, orchestrator function code must be deterministic. 有关业务流程协调程序函数的代码限制的详细信息,请参阅业务流程协调程序函数代码约束主题。For more information about code restrictions for orchestrator functions, see the orchestrator function code constraints topic.

备注

如果业务流程协调程序函数发出日志消息,重播行为可能导致发出重复的日志消息。If an orchestrator function emits log messages, the replay behavior may cause duplicate log messages to be emitted. 请参阅日志记录主题,详细了解此行为发生的原因及其解决方法。See the Logging topic to learn more about why this behavior occurs and how to work around it.

业务流程历史记录Orchestration history

Durable Task Framework 的事件溯源行为与编写的业务流程协调程序函数代码密切相关。The event-sourcing behavior of the Durable Task Framework is closely coupled with the orchestrator function code you write. 假设你有一个活动链接业务流程协调程序函数,如以下业务流程协调程序函数:Suppose you have an activity-chaining orchestrator function, like the following orchestrator function:

[FunctionName("E1_HelloSequence")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}

执行到每条 await (C#) 或 yield (JavaScript) 语句时,Durable Task Framework 会在某个持久存储后端(通常是 Azure 表存储)中创建该函数的执行状态检查点。At each await (C#) or yield (JavaScript) statement, the Durable Task Framework checkpoints the execution state of the function into some durable storage backend (typically Azure Table storage). 此状态称为“业务流程历史记录”。This state is what is referred to as the orchestration history.

历史记录表History table

一般而言,Durable Task Framework 会在每个检查点位置执行以下操作:Generally speaking, the Durable Task Framework does the following at each checkpoint:

  1. 将执行历史记录保存到 Azure 存储表中。Saves execution history into Azure Storage tables.
  2. 将业务流程协调程序想要调用的函数的相关消息排队。Enqueues messages for functions the orchestrator wants to invoke.
  3. 将业务流程协调程序本身的消息排队 — 例如,持久计时器消息。Enqueues messages for the orchestrator itself — for example, durable timer messages.

完成检查点之后,在业务流程协调程序函数需要执行其他工作之前,可以从内存中任意删除该函数。Once the checkpoint is complete, the orchestrator function is free to be removed from memory until there is more work for it to do.

备注

在将数据保存到表存储和队列的间隔期限内,Azure 存储不提供任何事务保证。Azure Storage does not provide any transactional guarantees between saving data into table storage and queues. 若要处理错误,Durable Functions 存储提供程序会使用“最终一致性”模式。To handle failures, the Durable Functions storage provider uses eventual consistency patterns. 这些模式可确保在发生崩溃时不会丢失数据,或者在创建检查点的中途不会断开连接。These patterns ensure that no data is lost if there is a crash or loss of connectivity in the middle of a checkpoint.

完成后,前面所示的函数历史记录在 Azure 表存储中如下表所示(为方便演示,此处采用了缩写):Upon completion, the history of the function shown earlier looks something like the following table in Azure Table Storage (abbreviated for illustration purposes):

PartitionKey (InstanceId)PartitionKey (InstanceId) EventTypeEventType 时间戳Timestamp 输入Input 名称Name 结果Result 状态Status
eaee885beaee885b ExecutionStartedExecutionStarted 2017-05-05T18:45:28.852Z2017-05-05T18:45:28.852Z Nullnull E1_HelloSequenceE1_HelloSequence
eaee885beaee885b OrchestratorStartedOrchestratorStarted 2017-05-05T18:45:32.362Z2017-05-05T18:45:32.362Z
eaee885beaee885b TaskScheduledTaskScheduled 2017-05-05T18:45:32.670Z2017-05-05T18:45:32.670Z E1_SayHelloE1_SayHello
eaee885beaee885b OrchestratorCompletedOrchestratorCompleted 2017-05-05T18:45:32.670Z2017-05-05T18:45:32.670Z
eaee885beaee885b TaskCompletedTaskCompleted 2017-05-05T18:45:34.201Z2017-05-05T18:45:34.201Z """Hello Tokyo!""""""Hello Tokyo!"""
eaee885beaee885b OrchestratorStartedOrchestratorStarted 2017-05-05T18:45:34.232Z2017-05-05T18:45:34.232Z
eaee885beaee885b TaskScheduledTaskScheduled 2017-05-05T18:45:34.435Z2017-05-05T18:45:34.435Z E1_SayHelloE1_SayHello
eaee885beaee885b OrchestratorCompletedOrchestratorCompleted 2017-05-05T18:45:34.435Z2017-05-05T18:45:34.435Z
eaee885beaee885b TaskCompletedTaskCompleted 2017-05-05T18:45:34.763Z2017-05-05T18:45:34.763Z """Hello Seattle!""""""Hello Seattle!"""
eaee885beaee885b OrchestratorStartedOrchestratorStarted 2017-05-05T18:45:34.857Z2017-05-05T18:45:34.857Z
eaee885beaee885b TaskScheduledTaskScheduled 2017-05-05T18:45:34.857Z2017-05-05T18:45:34.857Z E1_SayHelloE1_SayHello
eaee885beaee885b OrchestratorCompletedOrchestratorCompleted 2017-05-05T18:45:34.857Z2017-05-05T18:45:34.857Z
eaee885beaee885b TaskCompletedTaskCompleted 2017-05-05T18:45:34.919Z2017-05-05T18:45:34.919Z """Hello London!""""""Hello London!"""
eaee885beaee885b OrchestratorStartedOrchestratorStarted 2017-05-05T18:45:35.032Z2017-05-05T18:45:35.032Z
eaee885beaee885b OrchestratorCompletedOrchestratorCompleted 2017-05-05T18:45:35.044Z2017-05-05T18:45:35.044Z
eaee885beaee885b ExecutionCompletedExecutionCompleted 2017-05-05T18:45:35.044Z2017-05-05T18:45:35.044Z "[""Hello Tokyo!"",""Hello Seattle!"",""Hello London!""]""[""Hello Tokyo!"",""Hello Seattle!"",""Hello London!""]" 已完成Completed

有关列值的一些注释:A few notes on the column values:

  • PartitionKey:包含业务流程的实例 ID。PartitionKey: Contains the instance ID of the orchestration.
  • EventType:表示事件的类型。EventType: Represents the type of the event. 可为以下类型之一:May be one of the following types:
    • OrchestrationStarted:业务流程协调程序函数已从等待状态恢复,或者正首次运行。OrchestrationStarted: The orchestrator function resumed from an await or is running for the first time. Timestamp 列用于填充 CurrentUtcDateTime (.NET) 和 currentUtcDateTime (JavaScript) API 的确定性值。The Timestamp column is used to populate the deterministic value for the CurrentUtcDateTime (.NET) and currentUtcDateTime (JavaScript) APIs.
    • ExecutionStarted:业务流程协调程序函数已开始首次执行。ExecutionStarted: The orchestrator function started executing for the first time. 此事件也包含 Input 列中输入的函数。This event also contains the function input in the Input column.
    • TaskScheduled:已计划活动函数。TaskScheduled: An activity function was scheduled. Name 列中已捕获该活动函数的名称。The name of the activity function is captured in the Name column.
    • TaskCompleted:已完成活动函数。TaskCompleted: An activity function completed. Result 列中提供了该函数的结果。The result of the function is in the Result column.
    • TimerCreated:已创建持久计时器。TimerCreated: A durable timer was created. FireAt 列包含计时器过期时的 UTC 计划时间。The FireAt column contains the scheduled UTC time at which the timer expires.
    • TimerFired:已触发持久计时器。TimerFired: A durable timer fired.
    • EventRaised:已将外部事件发送到业务流程实例。EventRaised: An external event was sent to the orchestration instance. Name 列捕获事件的名称,Input 列捕获事件的有效负载。The Name column captures the name of the event and the Input column captures the payload of the event.
    • OrchestratorCompleted:处于等待状态的业务流程协调程序函数。OrchestratorCompleted: The orchestrator function awaited.
    • ContinueAsNew:业务流程协调程序函数已完成,并已使用新状态重启自身。ContinueAsNew: The orchestrator function completed and restarted itself with new state. Result 列包含用作已重启实例中的输入的值。The Result column contains the value, which is used as the input in the restarted instance.
    • ExecutionCompleted:业务流程协调程序函数已运行并已完成(或失败)。ExecutionCompleted: The orchestrator function ran to completion (or failed). 该函数的输出或错误详细信息存储在 Result 列中。The outputs of the function or the error details are stored in the Result column.
  • 时间戳:历史记录事件的 UTC 时间戳。Timestamp: The UTC timestamp of the history event.
  • 名称:调用的函数的名称。Name: The name of the function that was invoked.
  • 输入:函数的 JSON 格式的输入。Input: The JSON-formatted input of the function.
  • Result:函数的输出,即其返回值。Result: The output of the function; that is, its return value.

警告

尽管此表可以用作有效的调试工具,但不要对它有任何依赖。While it's useful as a debugging tool, don't take any dependency on this table. 它可能会随着 Durable Functions 扩展的演变而变化。It may change as the Durable Functions extension evolves.

每当函数从 await (C#) 或 yield (JavaScript) 恢复时,Durable Task Framework 会从头开始重新运行业务流程协调程序函数。Every time the function resumes from an await (C#) or yield (JavaScript), the Durable Task Framework reruns the orchestrator function from scratch. 每次重新运行时,它会查询执行历史记录,确定当前的异步操作是否已发生。On each rerun, it consults the execution history to determine whether the current async operation has taken place. 如果该操作已发生,该框架会立即重播该操作的输出,并转到下一个 await (C#) 或 yield (JavaScript)。If the operation took place, the framework replays the output of that operation immediately and moves on to the next await (C#) or yield (JavaScript). 此过程会持续到整个历史记录被重播为止。This process continues until the entire history has been replayed. 重播当前历史记录后,本地变量已还原到其先前值。Once the current history has been replayed, the local variables will have been restored to their previous values.

功能和模式Features and patterns

以下部分介绍业务流程协调程序函数的功能和模式。The next sections describe the features and patterns of orchestrator functions.

子业务流程Sub-orchestrations

业务流程协调程序函数可以调用活动函数,但也可以调用其他业务流程协调程序函数。Orchestrator functions can call activity functions, but also other orchestrator functions. 例如,可以基于业务流程协调程序函数库构建更大的业务流程。For example, you can build a larger orchestration out of a library of orchestrator functions. 或者,你可以并行运行某个业务流程协调程序函数的多个实例。Or, you can run multiple instances of an orchestrator function in parallel.

有关详细信息和示例,请参阅子业务流程一文。For more information and for examples, see the Sub-orchestrations article.

持久计时器Durable timers

业务流程可以计划持久计时器来实现延迟或设置处理异步操作时的超时。Orchestrations can schedule durable timers to implement delays or to set up timeout handling on async actions. 请在业务流程协调程序函数中使用持久计时器,而不要使用 Thread.SleepTask.Delay (C#) 或 setTimeout()setInterval() (JavaScript)。Use durable timers in orchestrator functions instead of Thread.Sleep and Task.Delay (C#) or setTimeout() and setInterval() (JavaScript).

有关详细信息和示例,请参阅持久计时器一文。For more information and for examples, see the Durable timers article.

外部事件External events

业务流程协调程序函数可以等待外部事件来更新业务流程实例。Orchestrator functions can wait for external events to update an orchestration instance. 此项 Durable Functions 功能通常用于处理人机交互或其他外部回调。This Durable Functions feature often is useful for handling a human interaction or other external callbacks.

有关详细信息和示例,请参阅外部事件一文。For more information and for examples, see the External events article.

错误处理。Error handling

业务流程协调程序函数可以使用编程语言的错误处理功能。Orchestrator functions can use the error-handling features of the programming language. 业务流程代码中支持诸如 try/catch 的现有模式。Existing patterns like try/catch are supported in orchestration code.

业务流程协调程序函数还可以将重试策略添加到它们调用的活动或子业务流程协调程序函数。Orchestrator functions can also add retry policies to the activity or sub-orchestrator functions that they call. 如果某个活动或子业务流程协调程序函数失败并出现异常,则指定的重试策略可以自动延迟并重试执行,直到达到指定的次数。If an activity or sub-orchestrator function fails with an exception, the specified retry policy can automatically delay and retry the execution up to a specified number of times.

备注

如果业务流程协调程序函数中出现未经处理的异常,则业务流程实例将以 Failed 状态完成。If there is an unhandled exception in an orchestrator function, the orchestration instance will complete in a Failed state. 业务流程实例在失败后不可重试。An orchestration instance cannot be retried once it has failed.

有关详细信息和示例,请参阅错误处理一文。For more information and for examples, see the Error handling article.

关键节(Durable Functions 2.x,当前仅限 .NET)Critical sections (Durable Functions 2.x, currently .NET only)

业务流程实例是单线程的,因此无需考虑业务流程内部的争用情况。Orchestration instances are single-threaded so it isn't necessary to worry about race conditions within an orchestration. 但是,当业务流程与外部系统交互时,可能会出现争用情况。However, race conditions are possible when orchestrations interact with external systems. 若要在与外部系统交互时缓解争用情况,业务流程协调程序函数可以使用 .NET 中的 LockAsync 方法定义关键节。To mitigate race conditions when interacting with external systems, orchestrator functions can define critical sections using a LockAsync method in .NET.

以下示例代码演示了一个定义关键节的业务流程协调程序函数。The following sample code shows an orchestrator function that defines a critical section. 它使用 LockAsync 方法进入关键节。It enters the critical section using the LockAsync method. 此方法要求向某个持久管理锁状态的持久实体传递一个或多个引用。This method requires passing one or more references to a Durable Entity, which durably manages the lock state. 此业务流程的单个实例每次只能执行关键节中的代码。Only a single instance of this orchestration can execute the code in the critical section at a time.

[FunctionName("Synchronize")]
public static async Task Synchronize(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var lockId = new EntityId("LockEntity", "MyLockIdentifier");
    using (await context.LockAsync(lockId))
    {
        // critical section - only one orchestration can enter at a time
    }
}

LockAsync 获取持久锁,并返回一个在释放时结束关键节的 IDisposableThe LockAsync acquires the durable lock(s) and returns an IDisposable that ends the critical section when disposed. 可将此 IDisposable 结果与 using 块一起使用,以获取关键节的语法表示形式。This IDisposable result can be used together with a using block to get a syntactic representation of the critical section. 当业务流程协调程序函数进入关键节时,只有一个实例可以执行该代码块。When an orchestrator function enters a critical section, only one instance can execute that block of code. 尝试进入关键节的任何其他实例将被阻止,直到上一个实例退出关键节。Any other instances that try to enter the critical section will be blocked until the previous instance exits the critical section.

关键节功能对于协调持久实体的更改也很有用。The critical section feature is also useful for coordinating changes to durable entities. 有关关键节的详细信息,请参阅持久实体的“实体协调”主题。For more information about critical sections, see the Durable entities "Entity coordination" topic.

备注

关键节在 Durable Functions 2.0 及更高版本中可用。Critical sections are available in Durable Functions 2.0 and above. 目前,只有 .NET 业务流程实现此功能。Currently, only .NET orchestrations implement this feature.

调用 HTTP 终结点 (Durable Functions 2.x)Calling HTTP endpoints (Durable Functions 2.x)

根据协调程序函数代码约束中所述,不允许业务流程协调程序函数执行 I/O。Orchestrator functions aren't permitted to do I/O, as described in orchestrator function code constraints. 此项限制的典型解决方法是将任何需要执行 I/O 的代码包装在某个活动函数中。The typical workaround for this limitation is to wrap any code that needs to do I/O in an activity function. 与外部系统交互的业务流程经常使用活动函数发出 HTTP 调用,并将结果返回给业务流程。Orchestrations that interact with external systems frequently use activity functions to make HTTP calls and return the result to the orchestration.

若要简化这种常见模式,业务流程协调程序函数可以使用 CallHttpAsync 方法直接调用 HTTP API。To simplify this common pattern, orchestrator functions can use the CallHttpAsync method to invoke HTTP APIs directly.

[FunctionName("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    Uri url = context.GetInput<Uri>();

    // Makes an HTTP GET request to the specified endpoint
    DurableHttpResponse response = 
        await context.CallHttpAsync(HttpMethod.Get, url);

    if (response.StatusCode >= 400)
    {
        // handling of error codes goes here
    }
}

除了支持基本请求/响应模式外,该方法还支持自动处理常见的异步 HTTP 202 轮询模式,并支持使用托管标识通过外部服务进行身份验证。In addition to supporting basic request/response patterns, the method supports automatic handling of common async HTTP 202 polling patterns, and also supports authentication with external services using Managed Identities.

有关详细信息和示例,请参阅 HTTP 功能一文。For more information and for detailed examples, see the HTTP features article.

备注

在 Durable Functions 2.0 及更高版本中可以直接从业务流程协调程序函数调用 HTTP 终结点。Calling HTTP endpoints directly from orchestrator functions is available in Durable Functions 2.0 and above.

传递多个参数Passing multiple parameters

无法直接将多个参数传递给一个活动函数。It isn't possible to pass multiple parameters to an activity function directly. 建议传入对象或复合对象的数组。The recommendation is to pass in an array of objects or composite objects.

在 .NET 中,还可以使用 ValueTuples 对象。In .NET you can also use ValueTuples objects. 以下示例使用了 C# 7 添加的 ValueTuples 的新功能:The following sample is using new features of ValueTuples added with C# 7:

[FunctionName("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string major = "ComputerScience";
    int universityYear = context.GetInput<int>();

    object courseRecommendations = await context.CallActivityAsync<object>(
        "CourseRecommendations",
        (major, universityYear));
    return courseRecommendations;
}

[FunctionName("CourseRecommendations")]
public static async Task<object> Mapper([ActivityTrigger] IDurableActivityContext inputs)
{
    // parse input for student's major and year in university
    (string Major, int UniversityYear) studentInfo = inputs.GetInput<(string, int)>();

    // retrieve and return course recommendations by major and university year
    return new
    {
        major = studentInfo.Major,
        universityYear = studentInfo.UniversityYear,
        recommendedCourses = new []
        {
            "Introduction to .NET Programming",
            "Introduction to Linux",
            "Becoming an Entrepreneur"
        }
    };
}

后续步骤Next steps