本文可帮助你诊断和修复使用可移植 Durable Task SDK 生成应用程序的常见问题。 这些 SDK 连接到 Durable Task Scheduler 后端,并在任何托管平台上运行,包括 Azure Container Apps、Kubernetes 和 VM。
注意
本指南介绍 可移植持久任务 SDK。 有关 Durable Task Scheduler 服务的具体问题,请参阅 Durable Task Scheduler 疑难解答。 有关特定于 Durable Functions 扩展的问题,请参阅 Durable Functions 故障排除指南。
Tip
Durable Task Scheduler 监视仪表板是检查业务流程状态、查看执行历史记录和识别失败的宝贵工具。 将它与本指南一起使用以加快故障排除速度。
连接和设置问题
仿真器未运行或无法访问
如果应用在启动时失败,并出现连接错误,例如“连接被拒绝”或“无法连接”,请检查 Durable Task Scheduler 模拟器是否正在运行且可访问。
检查模拟器 Docker 容器是否正在运行:
docker ps | grep durabletask核对正确的端口映射。 模拟器公开两个端口:
- 8080 — gRPC 终结点(供应用程序使用)
- 8082 - 仪表板 UI
如果使用自定义端口映射,请更新连接字符串以匹配主机端口到容器端口
8080的映射。测试与 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 taskHubName = "my-taskhub";
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddDurableTaskWorker(options =>
{
options.EnableEntitySupport = true;
})
.UseDurableTaskScheduler(connectionString, taskHubName);
builder.Services.AddDurableTaskClient()
.UseDurableTaskScheduler(connectionString, taskHubName);
任务中心不存在
如果编排无法启动或工作器已连接但无法处理任务,那么调度器上可能不存在任务集线器。 模拟器通常使用 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时失败,则问题可能与身份验证相关:
- 检查托管标识是否已分配给应用(系统分配或用户分配)。
- 检查标识在调度器资源或特定任务中心上是否具有 持久任务数据参与者 角色。
- 确保connection string使用正确的
Authentication值(ManagedIdentity)。 在 Python 中,将DefaultAzureCredential()实例作为token_credential参数传递,而不是使用connection string。 - 对于用户分配的标识,请检查“connection string”中的
ClientID是否与标识的客户端 ID 匹配。
有关详细说明,请参阅 Durable Task Scheduler 的基于标识的访问。
编排问题
协调任务处于挂起状态
处于“挂起”状态的编排表示已计划,但工作器尚未选取它。 检查以下项:
- 工作进程正在运行。 确保您的工作进程正在运行,并连接到编排任务被安排的同一任务中心。
- 任务中心名称匹配。 检查工作器和客户端是否都引用相同的任务中心端点名称。 不匹配会导致工作者轮询不同的任务中心。
- Orchestrator已注册。 必须在工作器中注册在计划时引用的协调程序函数或类。
检查协调器类是否在启动期间注册到工作线程。 如果使用源生成器([DurableTask] 属性),则注册是自动的。 否则,请手动注册:
builder.Services.AddDurableTaskWorker(builder =>
{
builder.AddTasks(tasks =>
{
tasks.AddOrchestrator<MyOrchestrator>();
tasks.AddActivity<MyActivity>();
});
});
业务流程停滞在“正在运行”状态
停滞在“运行中”的编排通常意味着它正在等待尚未完成的任务。 常见原因:
- 活动未注册。 编排调用了未向工作程序注册的活动名称。 检查 Durable Task Scheduler 仪表板是否存在错误。
- 正在等待外部事件。 该编排调用
waitForExternalEvent并且事件尚未触发。 - 等待持久计时器。 调度程序创建了一个尚未过期的计时器。
- 活动引发未处理的异常。 检查仪表板历史记录视图,了解失败的活动事件。
使用 Durable Task Scheduler 仪表板 检查业务流程的执行历史记录,并确定哪个任务正在阻止。
不确定性的编排器代码
业务流程协调程序代码必须是 确定性的。 非确定性代码会导致重播失败,导致意外行为、无限循环或错误。 避免编排器代码中的以下模式:
不要直接使用当前时间。 请改用上下文提供的当前时间。
// ❌ Wrong - non-deterministic
var now = DateTime.UtcNow;
// ✅ Correct - deterministic
var now = context.CurrentUtcDateTime;
请勿直接在业务流程协调程序代码中使用随机数、GUID 或 HTTP 调用。 将这些操作移动到活动中。
// ❌ Wrong - non-deterministic
var id = Guid.NewGuid();
// ✅ Correct - deterministic
var id = context.NewGuid();
// ❌ Wrong - I/O in orchestrator
var data = await httpClient.GetAsync("https://example.com/api");
// ✅ Correct - delegate to activity
var data = await context.CallActivityAsync<string>("FetchData");
序列化和反序列化错误
当用于业务流程输入、输出或活动结果的类型在调用方和被调用方之间不匹配时,会发生序列化错误。 常见原因包括:
- 类型不匹配。 协调程序期望的类型与活动返回的类型不同。
- 不可序列化的类型。 无法序列化为 JSON 的自定义类型会以无提示方式失败或引发异常。
- Java字符串的双序列化。 在Java中,将
String直接传递给活动可能会导致双引号字符串(例如,"\"hello\""而不是"hello")。 此行为是一个 已知问题。 显式强制转换结果或使用包装对象。
Tip
对业务流程和活动的输入和输出使用简单数据类型(字符串、数字、数组和纯对象或 POJOs/POCOs/dataclasses)。 避免使用自定义序列化逻辑的复杂类型。
活动问题
找不到活动
如果编排失败并出现“找不到活动”错误,则注册到工作程序的活动名称与编排代码中使用的名称不匹配。
在.NET中,活动可以通过类名或使用源生成器的 [DurableTask] 属性进行注册。 验证活动类是否包含在工作者注册中:
builder.Services.AddDurableTaskWorker(builder =>
{
builder.AddTasks(tasks =>
{
tasks.AddActivity<SayHello>();
});
});
从编排器调用活动时,使用类名:
string result = await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo");
处理活动故障
当活动引发异常时,协调器会收到一个 TaskFailedException(或语言对应)。 检查内部错误详细信息,了解根本原因。
try
{
await context.CallActivityAsync("ProcessOrder", order);
}
catch (TaskFailedException ex)
{
// Access the original exception details
var details = ex.FailureDetails;
logger.LogError(
"Activity failed: {Type} - {Message}",
details.ErrorType,
details.ErrorMessage);
// Check for specific exception types
if (details.IsCausedBy<TimeoutException>())
{
// Handle timeout
}
}
针对活动失败的重试策略
配置重试策略以自动重试失败的活动。 暂时性故障(如网络超时或临时服务中断)通常通过重试来解决。
var retryOptions = new TaskOptions(
new TaskRetryOptions(new RetryPolicy(
maxNumberOfAttempts: 3,
firstRetryInterval: TimeSpan.FromSeconds(5),
backoffCoefficient: 2.0)));
await context.CallActivityAsync("ProcessOrder", order, retryOptions);
gRPC 问题
gRPC 消息大小限制
gRPC 强制默认的最大消息大小为 4 MB。 具有大型输入或输出的业务流程或活动可能超出此限制,并失败并出现 RESOURCE_EXHAUSTED 或 message too large 错误。
缓解措施:
- 减小输入和输出的大小。 将大型有效负载存储在外部存储(如Azure Blob Storage)中,并仅传递引用。
- 将大型扇出结果分解为通过子协调程序处理的较小批次。
关闭期间的流取消错误
停止工作线程时,可能会看到流取消错误,例如 CANCELLED: Cancelled on client。 这些错误通常是无害的,并且会发生,因为工作者和调度器之间的 gRPC 流在关闭期间关闭。
SDK 在内部处理这些错误。 如果应用有意关闭,则无需执行任何操作。
日志记录和诊断
启用详细日志记录
增加日志详细级别以获取有关 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 源生成器可能会生成导致构建失败的警告(CS0419,VSTHRD105)。 禁止显示以下特定警告:
<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 |