Перенос приложения Устойчивые функции из процесса в изолированную рабочую модель (.NET)

В этом руководстве описывается перенос приложения .NET Устойчивые функции из модели внутрипроцессной в изолированную рабочую модель. Модель в процессе утратит поддержку 10 ноября 2026 г. После этой даты обновления системы безопасности или исправления ошибок не предоставляются. Изолированная рабочая модель также предоставляет полный контроль над процессами, стандартное внедрение зависимостей .NET и доступ к новейшим функциям платформы.

Предупреждение

Поддержка модели в процессе заканчивается 10 ноября 2026 г. Мы рекомендуем начать миграцию сейчас. Общие сведения об изолированной рабочей модели см. в разделе .NET обзор изолированного рабочего процесса.

Контрольный список миграции

Используйте следующий контрольный список для отслеживания хода выполнения каждого шага миграции:

Step Секция
1. Проверка предварительных требований Prerequisites
2. Обновление файла проекта Обновление файла проекта
Добавьте Program.cs Добавить Program.cs
4. Обновление ссылок на пакет Обновление ссылок на пакет
5. Обновление кода функции Обновление кода функции
6. Обновление local.settings.json Обновление local.settings.json
7. Локальное тестирование Локальное тестирование
8. Развертывание в Azure Развертывание в Azure

Необходимые условия

  • Функции Azure Core Tools версии 4.x или более поздней версии
  • пакет SDK .NET 8.0 (или целевая версия .NET)
  • Visual Studio 2022 или VS Code с расширением Функции Azure

Определение приложений для миграции (необязательно)

Если вы не уверены, какие приложения по-прежнему используют модель в процессе, запустите этот скрипт 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();

Обновление ссылок на пакет

сопоставление пакетов Устойчивые функции

Внутрипроцессный пакет Пакет изолированного работника
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

Important

Удалите все ссылки на пространства имен Microsoft.Azure.WebJobs.* и Microsoft.Azure.Functions.Extensions из проекта.

Обновление кода функции

В этом разделе рассматриваются изменения кода для каждого типа Устойчивые функции. Перейдите к разделу для типов функций, которые использует приложение:

Полное сопоставление API по 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))]

Изменения функции Orchestrator

До (в процессе):

[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
Logger ILogger Параметр context.CreateReplaySafeLogger()
Атрибут [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

Изменения политики повторных попыток

В процессе используется RetryOptions с CallActivityWithRetryAsync. Изолированный работник использует 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 см. в справочнике по API.

Предупреждение

Сериализация по умолчанию изменена: изолированный рабочий по умолчанию использует System.Text.Json вместо Newtonsoft.Json. Если в ваших оркестрациях передаются сложные объекты, тщательно проверьте сериализацию. См. различия сериализации JSON для параметров конфигурации.

Предупреждение

Продолжение изменения по умолчанию для AsNew: preserveUnprocessedEvents параметр по умолчанию изменился с false (2.x) на true (изолированный). Если ваша оркестрация использует ContinueAsNew и зависит от сброса необработанных событий, передайте preserveUnprocessedEvents: false явно.

Note

Изменение параметра по умолчанию для RestartAsync: restartWithNewInstanceId параметр по умолчанию был изменён с true (2.x) на false (изолированный). Если ваш код вызывает RestartAsync и зависит от создания нового идентификатора экземпляра, явно передайте restartWithNewInstanceId: true.

Другие заметные изменения:

  • Прокси-серверы сущностей удаленыCreateEntityProxy<T> недоступен. Используйте Entities.CallEntityAsync или Entities.SignalEntityAsync напрямую.
  • Удалены операции в узле задач — перегрузки, которые приняты taskHubName/connectionName, недоступны. Поддерживаются только те же операции центра задач.
  • Журнал оркестрации перемещенDurableOrchestrationStatus.History больше не находится в объекте состояния. Используйте DurableTaskClient.GetOrchestrationHistoryAsync.

Обновите local.settings.json

Ключевое изменение заключается в установке FUNCTIONS_WORKER_RUNTIME с dotnet на dotnet-isolated:

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

Note

Конфигурация серверной части хранилища (служба хранилища Azure, MSSQL, Netherite или планировщик устойчивых задач) не изменяется при миграции. Сохраните существующие параметры, связанные с хранилищем.

Локальное тестирование

Запустите функциональное приложение локально и убедитесь, что все оркестрации, действия и сущности работают корректно.

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 Ошибки.

Solution: Убедитесь, что удалите все ссылки на пакеты Microsoft.Azure.WebJobs.* и замените их изолированными рабочими эквивалентами.

Проблема: атрибут привязки не найден

Симптом:The type or namespace 'QueueTrigger' could not be found

Решение: Добавьте соответствующий пакет расширения и обновите инструкции using:

// 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. Дополнительные сведения см. в разделе behavioral changes справочника по API и Сериализация и сохраняемость в Устойчивые функции.

Чтобы использовать 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();

Note

Для этого подхода требуются пакеты NuGet Newtonsoft.Json и Azure.Core.Serialization.

Checklist

Используйте этот контрольный список, чтобы обеспечить полную миграцию:

  • Обновленный файл проекта с помощью <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.* using
  • Добавлено Microsoft.Azure.Functions.Worker с помощью инструкций
  • CreateEntityProxy<T> Заменено прямыми CallEntityAsync/SignalEntityAsync вызовами
  • Заменены перегрузки операций в концентраторе задач (если используются)
  • Заменены пакетные вызовы GetStatusAsync/PurgeInstanceHistoryAsync по ID на вызовы, основанные на фильтрах, или на отдельные вызовы.
  • Перенос доступа DurableOrchestrationStatus.History к GetOrchestrationHistoryAsync
  • Обновлены параметры конструктора сущности DispatchAsync для использования внедрения зависимостей (DI)
  • Тестирование всех функций локально
  • Развернуто в промежуточном слоте и проверено
  • Переключено на продакшн-среду

Дальнейшие действия