排查常见 Durable Task SDK 问题

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

常见场景

连接和设置

编排

活动

gRPC

日志记录和诊断

特定于语言

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

注意

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

Tip

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

找到您的问题

错误消息或症状 章节
connection refusedfailed to connect 在启动时 仿真器未运行或无法访问
启动时的连接字符串解析错误或身份验证错误 连接字符串格式不正确
工作进程连接,但编排未启动 任务中心不存在
Azure上的 401 Unauthorized 或身份/角色错误 基于身份的 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 的托管标识

编排问题

业务流程停滞在“挂起”状态

处于“挂起”状态的编排表示已计划,但工作器尚未选取它。 检查以下项:

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

检查协调器类是否在启动期间注册到工作线程。 如果使用源生成器([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")。 此行为是一个 已知问题。 显式强制转换结果或使用包装对象。

Tip

对业务流程和活动的输入和输出使用简单数据类型(字符串、数字、数组和纯对象或 POJOs/POCOs/dataclasses)。 避免使用自定义序列化逻辑的复杂类型。

活动问题

找不到活动

如果编排失败并出现“找不到活动”错误,则注册到工作程序的活动名称与编排代码中使用的名称不匹配。

在.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_EXHAUSTED 错误或 message 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 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 循环中引发

Durable Task Roslyn 分析器可能会在协调程序的 lambda 代码位于循环中时引发ArgumentNullExceptionforeach。 此行为是不影响运行时行为的 已知问题 。 更新到最新的分析器包版本以获取修补程序。

Java

Gradle 权限被拒绝错误

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

chmod +x gradlew

OrchestratorBlockedException(编排程序阻塞异常)

当协调程序代码执行阻塞操作时,SDK 检测到该操作可能是不确定的。 此异常是防止业务流程协调程序代码违反 业务流程协调程序代码约束的一种安全措施。

常见原因:

  • 在编排器代码中调用阻塞的外部 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}")

获取支持

有关问题和报告错误,请在相关 SDK 的GitHub存储库中提交问题。 报告故障时,包括:

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