你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

将 Durable Functions 应用从进程内迁移到独立工作模型 (.NET)

本指南将指导你将.NET Durable Functions应用从进程内模型迁移到独立的辅助角色模型。 进程内模型于 2026 年 11 月 10 日终止支持。 在该日期之后,不会提供安全更新或 bug 修复。 独立工作者模型还提供了完整的进程控制、标准.NET依赖注入功能,并可以访问最新的平台功能。

警告

对进程内模型的支持将于 2026 年 11 月 10 日结束。 建议立即迁移。 有关隔离工作者模型的背景信息,请参阅.NET隔离工作进程概述

迁移清单

使用以下清单跟踪每个迁移步骤的进度:

Step Section
1.验证先决条件 Prerequisites
2.更新项目文件 更新项目文件
3.添加Program.cs 添加Program.cs
4. 更新包引用 更新包引用
5. 更新函数代码 更新函数代码
6. 更新 local.settings.json 更新 local.settings.json
7. 在本地测试 在本地测试
8. 部署到 Azure 部署到 Azure

先决条件

  • Azure Functions Core Tools v4.x 或更高版本
  • .NET 8.0 SDK(或目标.NET版本)
  • Visual Studio 2022 或具有 Azure Functions 扩展名的 VS 代码

确定要迁移的应用(可选)

如果不确定哪些应用仍在使用进程内模型,请运行以下Azure PowerShell脚本:

$FunctionApps = Get-AzFunctionApp

$AppInfo = @{}

foreach ($App in $FunctionApps)
{
     if ($App.Runtime -eq 'dotnet')
     {
          $AppInfo.Add($App.Name, $App.Runtime)
     }
}

$AppInfo

显示dotnet作为运行时的应用程序使用进程内模型。 显示 dotnet-isolated 已使用独立工作模型的应用。

更新项目文件

之前(处理中)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.13.0" />
  </ItemGroup>
</Project>

之后(独立辅助角色)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.14.1" />
    <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext"/>
  </ItemGroup>
</Project>

主要更改是切换到可执行输出类型,并将所有 Microsoft.Azure.WebJobs.* 包替换为其Microsoft.Azure.Functions.Worker.*等效项。

添加Program.cs

独立工作者模型需要一个入口点 Program.cs。 在项目根目录中创建此文件。 如果你在 FunctionsStartup 中有一个 Startup.cs 类,请将这些服务注册移到 ConfigureServices 块中并删除 Startup.cs

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        
        // Add your custom services here (previously in FunctionsStartup)
        // services.AddSingleton<IMyService, MyService>();
    })
    .Build();

host.Run();

更新包引用

Durable Functions 包映射

进程中的软件包 隔离工作器包
Microsoft.Azure.WebJobs.Extensions.DurableTask Microsoft.Azure.Functions.Worker.Extensions.DurableTask
Microsoft.DurableTask.SqlServer.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer
Microsoft.Azure.DurableTask.Netherite.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite

常见扩展包映射

进行中 独立工人
Microsoft.Azure.WebJobs.Extensions.Storage Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs.Queues.Tables
Microsoft.Azure.WebJobs.Extensions.CosmosDB Microsoft.Azure.Functions.Worker.Extensions.CosmosDB
Microsoft.Azure.WebJobs.Extensions.ServiceBus Microsoft.Azure.Functions.Worker.Extensions.ServiceBus
Microsoft.Azure.WebJobs.Extensions.EventHubs Microsoft.Azure.Functions.Worker.Extensions.EventHubs
Microsoft.Azure.WebJobs.Extensions.EventGrid Microsoft.Azure.Functions.Worker.Extensions.EventGrid

重要

从项目中删除对 Microsoft.Azure.WebJobs.* 命名空间和 Microsoft.Azure.Functions.Extensions 的任何引用。

更新函数代码

本部分介绍每个Durable Functions类型的代码更改。 跳转到应用使用的函数类型的部分:

有关每个 API 的完整映射,请参阅 API 参考

命名空间更改

// Before (In-Process)
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;

// After (Isolated Worker)
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Entities;

函数属性更改

// Before (In-Process)
[FunctionName("MyOrchestrator")]

// After (Isolated Worker)
[Function(nameof(MyOrchestrator))]

编排器功能更改

之前(处理中):

[FunctionName("OrderOrchestrator")]
public static async Task<OrderResult> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context,
    ILogger log)
{
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

之后(隔离工作者):

[Function(nameof(OrderOrchestrator))]
public static async Task<OrderResult> OrderOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    ILogger logger = context.CreateReplaySafeLogger(nameof(OrderOrchestrator));
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

主要差异

方面 进行中 单独作业人员
上下文类型 IDurableOrchestrationContext TaskOrchestrationContext
记录器 ILogger 参数 context.CreateReplaySafeLogger()
Attribute [FunctionName] [Function]

活动函数更改

之前(处理中):

[FunctionName("ValidateOrder")]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    ILogger log)
{
    log.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

之后(隔离工作者):

[Function(nameof(ValidateOrder))]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger(nameof(ValidateOrder));
    logger.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

客户端函数更改

之前(处理中):

[FunctionName("StartOrder")]
public static async Task<IActionResult> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
    [DurableClient] IDurableOrchestrationClient client,
    ILogger log)
{
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.StartNewAsync("OrderOrchestrator", order);
    
    return client.CreateCheckStatusResponse(req, instanceId);
}

之后(隔离工作者):

[Function("StartOrder")]
public static async Task<HttpResponseData> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("StartOrder");
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        nameof(OrderOrchestrator), 
        order
    );
    
    return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

客户端类型更改

进行中 独立工人
IDurableOrchestrationClient DurableTaskClient
StartNewAsync() ScheduleNewOrchestrationInstanceAsync()
CreateCheckStatusResponse() CreateCheckStatusResponseAsync()
HttpRequest / IActionResult HttpRequestData / HttpResponseData

重试策略更改

进程内用法 RetryOptionsCallActivityWithRetryAsync. 隔离工作器使用 TaskOptions 配合标准 CallActivityAsync

之前(处理中):

var retryOptions = new RetryOptions(
    firstRetryInterval: TimeSpan.FromSeconds(5),
    maxNumberOfAttempts: 3);

string result = await context.CallActivityWithRetryAsync<string>(
    "MyActivity", retryOptions, input);

之后(隔离工作者):

var retryOptions = new TaskOptions(
    new TaskRetryOptions(new RetryPolicy(
        maxNumberOfAttempts: 3,
        firstRetryInterval: TimeSpan.FromSeconds(5))));

string result = await context.CallActivityAsync<string>(
    "MyActivity", input, retryOptions);

实体函数更改

之前(处理中):

[FunctionName(nameof(Counter))]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
            break;
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

之后(隔离工作者):

[Function(nameof(Counter))]
public static Task Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
{
    return dispatcher.DispatchAsync<CounterEntity>();
}

public class CounterEntity
{
    public int Value { get; set; }
    
    public void Add(int amount) => Value += amount;
    public int Get() => Value;
}

中断性行为变更

在测试迁移的应用之前,请查看这些更改。 有关每个 API 的完整映射,请参阅 API 参考

警告

序列化默认值已更改:独立辅助角色默认使用 System.Text.Json ,而不是 Newtonsoft.Json。 如果你的业务流程传递复杂对象,请仔细测试序列化。 有关配置选项 ,请参阅 JSON 序列化差异

警告

ContinueAsNew 默认更改preserveUnprocessedEvents 参数默认值从 false (2.x) 更改为 true (独立)。 如果编排使用 ContinueAsNew 且依赖于丢弃未处理事件,请显式传递 preserveUnprocessedEvents: false

注释

RestartAsync 默认更改restartWithNewInstanceId 参数默认值从 true (2.x) 更改为 false (独立)。 如果代码调用 RestartAsync 并依赖于正在生成的新实例 ID,请显式传递 restartWithNewInstanceId: true

其他值得注意的更改:

  • 删除实体代理 - CreateEntityProxy<T> 不可用。 请直接使用Entities.CallEntityAsyncEntities.SignalEntityAsync
  • 已删除跨任务中心操作 - 接受 taskHubName/connectionName 的重载不可用。 仅支持相同任务中心的操作。
  • 业务流程历史记录已移动 - DurableOrchestrationStatus.History 不再位于状态对象中。 使用 DurableTaskClient.GetOrchestrationHistoryAsync

更新 local.settings.json

主要更改是将FUNCTIONS_WORKER_RUNTIMEdotnet设置为dotnet-isolated

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
    }
}

注释

迁移不会更改存储后端配置(Azure 存储、MSSQL、Netherite 或 Durable Task Scheduler)。 保留与存储相关的现有设置。

在本地测试

在本地运行函数应用并验证所有业务流程、活动和实体是否正常工作。

func start

验证功能

根据情况测试以下方案:

  1. 使用 HTTP 触发器启动编排
  2. 监视编排状态
  3. 验证活动执行顺序
  4. 测试实体操作(如果适用的情况下)
  5. 检查 Application Insights 的遥测数据

部署到 Azure 云

使用部署槽位将停机时间降到最低:

  1. 为函数应用创建过渡槽
  2. 更新过渡槽配置:
    • FUNCTIONS_WORKER_RUNTIME 设置为 dotnet-isolated
    • 根据需要更新.NET堆栈版本。
  3. 将迁移的代码 部署到过渡槽。
  4. 在预发布槽中进行充分测试
  5. 执行槽交换以将更改移动到生产环境。

更新应用程序设置

在 Azure 门户中或者通过 CLI:

az functionapp config appsettings set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --settings FUNCTIONS_WORKER_RUNTIME=dotnet-isolated

更新堆栈配置

如果面向其他 .NET 版本,请按照以下步骤进行操作:

az functionapp config set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --net-framework-version v8.0

常见迁移问题

问题:程序集加载错误

症状:Could not load file or assembly 错误。

解决方案:确保删除所有 Microsoft.Azure.WebJobs.* 包引用,并将其替换为隔离工作器等效项。

问题:找不到绑定属性

症状:The type or namespace 'QueueTrigger' could not be found

解决方案:添加相应的扩展包并更新使用语句:

// Add using statement
using Microsoft.Azure.Functions.Worker;

// Install package
// dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues

问题:找不到 IDurableOrchestrationContext

症状:The type or namespace 'IDurableOrchestrationContext' could not be found

解决 方案: 替换为 TaskOrchestrationContext

using Microsoft.DurableTask;

[Function(nameof(MyOrchestrator))]
public static async Task MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
    // ...
}

问题:JSON 序列化差异

症状: 序列化错误或意外的数据格式

解决 方案: 独立模型默认使用 System.Text.Json 。 在Program.cs中配置序列化

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.Configure<JsonSerializerOptions>(options => {
            options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        });
    })
    .Build();

改用 Newtonsoft.Json:

services.Configure<WorkerOptions>(options => {
    options.Serializer = new NewtonsoftJsonObjectSerializer();
});

问题:迁移自定义序列化设置

症状:你在进程内模型中使用了 IMessageSerializerSettingsFactory,并需要在独立辅助角色中找到等效方案。

解决方案:Program.cs中配置工作者级别序列化程序。 有关详细信息,请参阅 API 参考中的行为更改部分,以及Durable Functions 中的序列化和持久性。

若要将 Newtonsoft.Json 与自定义设置配合使用,请执行以下操作:

// Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.Configure<WorkerOptions>(options =>
        {
            var settings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.None,
                DateFormatHandling = DateFormatHandling.IsoDateFormat,
            };
            options.Serializer = new NewtonsoftJsonObjectSerializer(settings);
        });
    })
    .Build();

注释

此方法需要 Newtonsoft.JsonAzure.Core.Serialization NuGet 包。

清单

使用此清单来确保完成迁移:

  • 更新了包含<OutputType>Exe</OutputType>的项目文件
  • Microsoft.NET.Sdk.Functions 替换为工作器包
  • 已将 Microsoft.Azure.WebJobs.Extensions.DurableTask 替换为独立包
  • 使用主机配置创建 Program.cs
  • 已删除 FunctionsStartup 类(如果存在)
  • 已全部 [FunctionName] 更新为 [Function]
  • IDurableOrchestrationContext替换为TaskOrchestrationContext
  • IDurableOrchestrationClient替换为DurableTaskClient
  • 更新了日志记录以使用 DI 或 FunctionContext
  • 已使用 local.settings.json 运行时更新 dotnet-isolated
  • 移除所有 Microsoft.Azure.WebJobs.* 使用语句
  • 添加了 Microsoft.Azure.Functions.Worker using 语句
  • CreateEntityProxy<T> 替换为直接 CallEntityAsync/SignalEntityAsync 调用
  • 替换跨任务中心操作重载(如果使用)
  • 将按 ID 批处理的 GetStatusAsync/PurgeInstanceHistoryAsync 调用替换为基于筛选器或单独调用
  • DurableOrchestrationStatus.History的访问已迁移到GetOrchestrationHistoryAsync
  • 更新了实体 DispatchAsync 构造函数参数以使用 DI
  • 在本地测试所有函数
  • 部署到预发布槽并验证
  • 交换到生产环境

后续步骤