我有一个基于 .NET 8 Worker Service 中的 cron 表达式的 Quartz.NET 重复作业。该作业应该调用 2 个外部 API,然后对下游的 API 响应执行一些处理。
为了提高吞吐量,我使用
Task Parallel Library
调用了 API。因此,我必须等待这两个任务完成才能开始处理。实现看起来有点像这样:
private void GetCarStatusAndPositionalInformation(
IJobExecutionContext context,
string terminalNumber)
{
CarInfo? carInfo = null; CarStatusInfo? carStatusInfo = null;
try
{
var getCarInformation = Task.Run(async () =>
{
var carInfoResponse = await _apiClient.QueryCarInformation(
terminalNumber, context.CancellationToken);
if (carInfoResponse?.CarInfo != null)
{
carInfo = carInfoResponse.CarInfo;
}
await Task.CompletedTask;
},
context.CancellationToken)
.ContinueWith(
continuationAction =>
{
_logger.LogWarning("Failed to get car info for terminal number: {terminalNumber}", terminalNumber);
},
context.CancellationToken,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.Current);
var getCarStatusInformation = Task.Run(async () =>
{
var carStatusInfoResponse = await _apiClient.GetCarStatusInfoAsync(
terminalNumber, context.CancellationToken);
if (carStatusInfoResponse?.CarStatusInfo != null)
{
carStatusInfo = carStatusInfoResponse.CarStatusInfo;
}
await Task.CompletedTask;
},
context.CancellationToken)
.ContinueWith(
continuationAction =>
{
_logger.LogWarning("Failed to get car info for terminal number: {terminalNumber}", terminalNumber);
},
context.CancellationToken,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.Current);
Task.WaitAll(
[getCarInformation, getCarStatusInformation], timeout: TimeSpan.FromSeconds(30));
if (carInfo != null) && carStatusInfo != null)
{
// Do some processing
}
}
catch (AggregateException aex)
{
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to obtain car status & positional information for: {terminalNumber}", terminalNumber);
}
}
为什么我总是收到
AggregateException
的 InnerException 消息,说任务已被取消?
问题在于您构建任务的方式。
getCarInformation
和 getCarStatusInformation
实际上引用了 延续,而不是您期望的任务。
现在,“快速而肮脏”的解决方案是采取两个步骤:
var getCarInformation = Task.Run(async () =>
{
var carInfoResponse = await _apiClient.QueryCarInformation(
terminalNumber, context.CancellationToken);
if (carInfoResponse?.CarInfo != null)
{
carInfo = carInfoResponse.CarInfo;
}
await Task.CompletedTask; // <- this makes no sense, btw
},
context.CancellationToken);
// ^^ Now you are referencing the Task you expected to reference
getCarInformation
.ContinueWith(
continuationAction =>
{
_logger.LogWarning("Failed to get car info for terminal number: {terminalNumber}", terminalNumber);
},
context.CancellationToken,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.Current);
但是更清洁就是根本不使用ContinueWith
。只需将失败日志移至任务本身即可。