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

排查常见持久任务 SDK 问题

本文可帮助你诊断和修复使用可移植 Durable Task SDK 生成应用程序的常见问题。 在以下列表中查找你的方案,并按照链接的步骤诊断并解决问题。

常见场景

连接和设置

业务流程

活动

gRPC

日志记录和诊断

特定于语言

这些 SDK 连接到 Durable Task Scheduler 后端,并在任何托管平台上运行,包括 Azure 容器应用、Kubernetes 和 VM。

注释

本指南介绍 可移植持久任务 SDK。 有关 Durable Task Scheduler 服务的具体问题,请参阅 Durable Task Scheduler 疑难解答。 有关特定于 Durable Functions 扩展的问题,请参阅 Durable Functions 故障排除指南

小窍门

Durable Task Scheduler 监视仪表板可用于检查业务流程状态、查看执行历史记录和识别失败。 将它与本指南一起使用以加快故障排除速度。

找到您的问题

错误消息或症状 Section
connection refusedfailed to connect 在启动时 仿真器未运行或无法访问
启动时的连接字符串解析错误或身份验证错误 连接字符串格式不正确
辅助角色已连接但业务流程未启动 任务中心不存在
401 Unauthorized 或 Azure 上的标识/角色错误 基于身份的 Azure 身份验证失败
业务流程卡在“挂起”状态 业务流程卡在“挂起”状态
业务流程卡在“运行中”状态 业务流程卡在“运行中”状态
重播失败、无限循环或意外行为 不确定的业务流程协调程序代码
类型不匹配或 JSON 序列化错误 序列化和反序列化错误
activity not found 找不到活动
RESOURCE_EXHAUSTEDmessage too large 超出 gRPC 消息大小限制
CANCELLED: Cancelled on client 关闭期间 关闭期间的流取消错误
CS0419 / VSTHRD105 警告导致构建失败 源生成器警告导致生成失败 (C#)
OrchestratorBlockedException (Java) OrchestratorBlockedException (Java)
使用 retry_policy 时出现无用错误(Python) 重试策略需要max_retry_interval(Python)

连接和设置问题

仿真器未运行或无法访问

如果应用在启动时失败,并出现连接错误,例如“连接被拒绝”或“无法连接”,请检查 Durable Task Scheduler 模拟器是否正在运行且可访问。

  1. 检查模拟器 Docker 容器是否正在运行:

    docker ps | grep durabletask
    
  2. 验证端口映射是否正确。 模拟器公开两个端口:

    • 8080 — gRPC 终结点(供应用程序使用)
    • 8082 - 仪表板 UI

    如果使用自定义端口映射,请更新连接字符串以匹配主机端口到容器端口 8080 的映射。

  3. 测试与 gRPC 终结点的连接:

    curl -v http://localhost:8080
    

    连接拒绝表示容器未运行或端口映射不正确。

连接字符串格式不正确

连接字符串错误是启动失败的常见原因。 请检查您的连接字符串是否符合预期格式。

本地开发 (模拟器):

Endpoint=http://localhost:8080;Authentication=None

Azure (托管标识):

Endpoint=https://<scheduler-name>.durabletask.io;Authentication=ManagedIdentity

Azure(用户分配的托管标识):

Endpoint=https://<scheduler-name>.durabletask.io;Authentication=ManagedIdentity;ClientID=<client-id>

常见错误:

  • https 用于本地模拟器(模拟器使用http
  • 对 Azure 端点使用 http(Azure 需要 https
  • 省略 Authentication 参数
  • 使用仪表板端口 (8082) 而不是 gRPC 端口 (8080

客户端或工作程序无法连接

检查客户端和工作器是否配置了正确的连接字符串和任务中心名称。

using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;

var connectionString = "Endpoint=http://localhost:8080;Authentication=None";

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddDurableTaskWorker()
    .AddTasks(registry =>
    {
        registry.AddOrchestrator<MyOrchestrator>();
        registry.AddActivity<MyActivity>();
    })
    .UseDurableTaskScheduler(connectionString);

builder.Services.AddDurableTaskClient()
    .UseDurableTaskScheduler(connectionString);

任务中心不存在

如果编排无法启动或工作器已连接但无法处理任务,那么调度器上可能不存在任务集线器。 模拟器通常使用 DTS_TASK_HUB_NAMES 环境变量自动创建任务中心。

检查模拟器是否已使用正确的任务中心名称启动:

docker run -d -p 8080:8080 -p 8082:8082 \
  -e DTS_TASK_HUB_NAMES="my-taskhub" \
  mcr.microsoft.com/dts/dts-emulator:latest

对于Azure托管的计划程序,请使用Azure CLI创建任务中心:

az durabletask taskhub create \
  --resource-group <resource-group> \
  --scheduler-name <scheduler-name> \
  --name <taskhub-name>

Azure上基于标识的身份验证失败

如果应用在本地运行,但在部署到Azure时失败,则问题可能与身份验证相关:

  1. 检查托管标识是否已分配给应用(系统分配或用户分配)。
  2. 检查该标识是否在调度器资源或特定任务中心上具有持久任务数据参与者角色。
  3. 确保连接字符串使用正确的 Authentication 值(ManagedIdentity)。 在 Python 中,将 DefaultAzureCredential() 实例作为 token_credential 参数传递,而不是使用连接字符串。
  4. 对于用户分配的标识,请检查连接字符串中的 ClientID 是否与该标识的客户端 ID 匹配。

有关详细说明,请参阅 配置 Durable Task Scheduler 的托管标识

编排问题

业务流程卡在“挂起”状态

处于“等待”状态的编排表示它已被调度,但尚未被工作器拾取。 检查以下项:

  • 工作器正在运行。 确保工作器进程正在运行,并连接到调度编排时使用的同一任务中心。
  • 任务中心名称匹配。 检查工作器和客户端是否都引用相同的任务中心端点名称。 名称不匹配会导致工作器轮询不同的任务中心。
  • 编排器已注册。 在调度时引用的编排器函数或类必须在工作器中注册。

检查编排器类是否在启动期间注册到工作器。 如果使用源生成器([DurableTask] 属性),则注册是自动的。 否则,请手动注册:

builder.Services.AddDurableTaskWorker()
    .AddTasks(registry =>
    {
        registry.AddOrchestrator<MyOrchestrator>();
        registry.AddActivity<MyActivity>();
    })
    .UseDurableTaskScheduler(connectionString);

业务流程卡在“运行中”状态

卡在“运行”状态的编排通常意味着它正在等待一个尚未完成的任务。 若要诊断,请打开 Durable Task Scheduler 仪表板 并检查业务流程的执行历史记录。 查找最后完成的事件 — 序列中的下一个事件就是阻塞的事件。

常见原因:

  • 活动未注册。 编排调用了未在工作器中注册的活动名称。 仪表板显示一个 TaskScheduled 事件,没有对应的 TaskCompleted。 检查业务流程协调程序代码和辅助角色注册之间的活动名称是否匹配(请参阅未找到活动)。
  • 正在等待外部事件。 编排调用 waitForExternalEvent 且该事件尚未触发。 仪表板显示预期但缺失的 EventRaised 事件。 验证事件名称以及发送者是否针对正确的业务流程实例 ID。
  • 等待持久计时器。 调度程序创建了一个尚未过期的计时器。 仪表板显示一个 TimerCreated 事件。 等待计时器触发,或检查计时器持续时间是否超过预期。
  • 活动抛出了未处理的异常。 仪表板显示一个 TaskFailed 事件。 检查失败详细信息中的异常消息和堆栈跟踪。

不确定性的编排器代码

业务流程协调程序代码必须是 确定性的。 非确定性代码会导致重播失败,导致意外行为、无限循环或错误。 请勿直接在业务流程协调程序代码中使用当前时间、随机数、GUID 或 I/O(如 HTTP 调用)。 使用上下文提供的替代方法或委托给活动。

// ❌ Wrong - non-deterministic
var now = DateTime.UtcNow;
var id = Guid.NewGuid();
var data = await httpClient.GetAsync("https://example.com/api");

// ✅ Correct - deterministic
var now = context.CurrentUtcDateTime;
var id = context.NewGuid();
var data = await context.CallActivityAsync<string>("FetchData");

序列化和反序列化错误

当用于业务流程输入、输出或活动结果的类型在调用方和被调用方之间不匹配时,会发生序列化错误。 这些错误可能表现为业务流程历史记录中意外的 null 值、JsonException 或类型转换失败。

诊断方法:

  1. 打开 Durable Task Scheduler 仪表板 并检查业务流程历史记录。 查看失败活动的 InputResult 字段。
  2. 验证协调器预期的类型是否与活动返回的类型匹配。 例如,如果活动返回一个string但协调程序期望一个int,则反序列化将失败。
  3. 检查不可序列化的类型。 无法序列化为 JSON 的自定义类型(例如,具有循环引用的类型或无默认构造函数)会以无提示方式失败或引发异常。

已知问题(Java):String直接传递给某个进程,可能会导致双引号字符串(例如,"\"hello\""而不是"hello")。 此行为是一个 已知问题。 显式转换结果或使用包装对象。

小窍门

在编排和活动的输入输出中使用简单数据类型(字符串、数字、数组,以及普通对象或 POJO/POCO/dataclass)。 避免使用自定义序列化逻辑的复杂类型。

活动问题

找不到活动

如果编排因“未发现活动”错误失败,则说明在工作器中注册的活动名称与编排代码中使用的名称不匹配。

在.NET中,活动可以通过类名或使用源生成器的 [DurableTask] 属性进行注册。 验证活动类是否包含在工作者注册中:

builder.Services.AddDurableTaskWorker()
    .AddTasks(registry =>
    {
        registry.AddActivity<SayHello>();
    })
    .UseDurableTaskScheduler(connectionString);

从编排器调用活动时,使用类名:

string result = await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo");

活动失败处理

当活动引发异常时,协调器会收到一个 TaskFailedException(或语言对应)。 捕获此异常并检查内部错误详细信息以查找根本原因。 在 C# 中,用于 ex.FailureDetails 访问错误类型和消息,以及 IsCausedBy<T>() 检查特定异常类型。

有关每种语言的详细错误处理和重试策略示例,请参阅 错误处理和重试

gRPC 问题

超出 gRPC 消息大小限制

如果看到 RESOURCE_EXHAUSTEDmessage too large 错误,说明业务流程或活动输入/输出超过了 gRPC 默认 4 MB 的最大消息大小。

缓解措施:

  • 减小输入和输出的大小。 将大型有效负载存储在外部存储(如Azure Blob 存储)中,并仅传递引用。
  • 将大型扇出结果拆分为较小的批次,并通过子编排进行处理。

关闭期间的流取消错误

停止辅助角色时,你可能会看到 CANCELLED: Cancelled on client 错误。 这些错误通常是无害的,因为在关闭过程中,工作器与调度程序之间的 gRPC 流会关闭。 .NET、Python和Java SDK 在内部处理这些错误。

JavaScript 中,SDK 在调用Stream error Error: 1 CANCELLED: Cancelled on client时可能会引发worker.stop()。 此错误是一个 已知问题。 如果该错误影响关闭逻辑,请在“停止”调用外层使用 try-catch 包装:

try {
  await worker.stop();
} catch (error) {
  // Ignore stream cancellation errors during shutdown
  if (!error.message.includes("CANCELLED")) {
    throw error;
  }
}

日志记录和诊断

详细日志记录配置

提高日志详细级别,以获取有关 SDK 操作的更多详细信息,包括 gRPC 通信和编排重放事件。

appsettings.json 或日志配置文件中:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.DurableTask": "Debug"
    }
  }
}

使用安全重播日志在编排重播期间避免重复日志条目。

public override async Task<string> RunAsync(
    TaskOrchestrationContext context, string input)
{
    ILogger logger = context.CreateReplaySafeLogger<MyOrchestrator>();
    logger.LogInformation("Processing input: {Input}", input);
    // ...
}

Application Insights 集成

对于生产应用程序,请将 Application Insights 配置为从 Durable Task SDK 应用程序收集遥测数据。 集成方法取决于托管平台:

托管平台 安装说明
Azure 容器应用 使用 Log Analytics 监视 Azure 容器应用 中的日志
Azure App 服务 为 Azure 应用服务中的应用启用诊断日志
Azure Kubernetes 服务 监视 Azure Kubernetes 服务

有关诊断的详细信息,请参阅 Durable Task SDK 中的诊断

特定于语言的问题

C#

代码生成器警告导致构建失败

如果在项目中使用 <TreatWarningsAsErrors>true</TreatWarningsAsErrors>,Durable Task 源生成器可能会生成导致构建失败的警告(CS0419VSTHRD105)。 禁止显示以下特定警告:

<PropertyGroup>
  <NoWarn>$(NoWarn);CS0419;VSTHRD105</NoWarn>
</PropertyGroup>

此已知问题正在GitHub上跟踪,并将在即将发布的版本中得到解决。

Roslyn 分析器在 foreach 循环中抛出异常

当编排器 lambda 代码位于 ArgumentNullException 循环中时,持久任务 Roslyn 分析器可能会抛出 foreach。 此行为是不影响运行时行为的 已知问题 。 更新到最新的分析器包版本以获取修补程序。

Java

Gradle 权限被拒绝错误

在 macOS 或 Linux 上,运行 ./gradlew 可能会失败并出现“权限被拒绝”错误。 通过将文件设置为可执行来修复此错误:

chmod +x gradlew

OrchestratorBlockedException(编排程序阻塞异常)

当编排器代码执行 SDK 检测为可能具有非确定性的阻塞操作时,会发生 OrchestratorBlockedException。 此异常是防止业务流程协调程序代码违反 业务流程协调程序代码约束的一种安全措施。

常见原因:

  • 在编排器代码中调用阻塞的外部 API。
  • 直接使用Thread.sleep(),而不是使用ctx.createTimer()
  • 在编排器代码中执行文件或网络 I/O。

将所有阻塞或 I/O 操作移动到活动函数中。

Python

重试策略需要最大重试间隔(max_retry_interval)

在Python中配置 retry_policy时,省略 max_retry_interval 参数将生成一个错误,该错误不会明确指示原因。 始终指定 max_retry_interval

from datetime import timedelta
from durabletask import task

retry_policy = task.RetryPolicy(
    max_number_of_attempts=3,
    first_retry_interval=timedelta(seconds=5),
    max_retry_interval=timedelta(minutes=1),  # Required
)

WhenAllTask 异常行为

用于 when_all 并行运行多个任务时,如果一个或多个任务失败,异常行为可能与预期不匹配。 只会引发第一个异常,其余任务异常可能会丢失。 如果需要完整的错误信息,请检查单个任务结果:

tasks = [ctx.call_activity(process_item, input=item) for item in items]
try:
    results = yield task.when_all(tasks)
except TaskFailedError as e:
    # Only the first failure is raised
    # Check individual tasks for comprehensive error handling
    print(f"At least one task failed: {e}")

获取支持

如有疑问或需要报告 bug,请在相关 SDK 的 GitHub 仓库中提交问题。 报告故障时,包括:

  • 受影响的协调实例 ID
  • 显示问题的 UTC 时间范围
  • 应用程序名称和部署区域(如果相关)
  • SDK 版本和托管平台
  • 相关日志或错误消息
SDK GitHub存储库
.NET microsoft/durabletask-dotnet
Java microsoft/durabletask-java
JavaScript microsoft/durabletask-js
Python microsoft/durabletask-python