我想用WhenAll
(不是一个一个)运行所有任务。
但之后我需要根据结果更新列表(LastReport
属性)。
我认为我有解决方案,但我想检查是否有更好的方法。
想法是:
我的解决方案是:
var lastReportAllTasks = new List<Task<Dictionary<string, string>>>();
var configurationTaskRelation = new Dictionary<int, Task<Dictionary<string, string>>>();
foreach (var configuration in MachineConfigurations)
{
var task = machineService.GetReports(configuration);
lastReportAllTasks.Add(task);
configurationTaskRelation.Add(configuration.Id, task);
}
await Task.WhenAll(lastReportAllTasks);
foreach (var configuration in MachineConfigurations)
{
var lastReportTask = configurationTaskRelation[configuration.Id];
configuration.LastReport = await lastReportTask;
}
答案 0 :(得分:5)
Select
函数本身可以是异步的。您可以等待报告并返回配置并返回相同的结果对象(匿名类型或元组,无论您喜欢什么):
var tasks=MachineConfigurations.Select(async conf=>{
var report= await machineService.GetReports(conf);
return new {conf,report});
var results=await Task.WhenAll(tasks);
foreach(var pair in results)
{
pair.conf.LastReport=pair.report;
}
编辑 - 循环和错误处理
正如Servy建议的那样,Task.WhenAll
可以被省略,等待可以在循环中移动:
foreach(var task in tasks)
{
var pair=await task;
pair.conf.LastReport=pair.report;
}
任务仍然会同时执行。但是在异常情况下,某些配置对象将被修改而某些配置对象不会被修改。
通常,这将是一个丑陋的情况,需要额外的异常处理代码来清理修改后的对象。当完成修改 on-the-side 并在快乐路径完成时完成/应用时,异常处理会更容易。这是更新Select()
内部配置对象的一个原因,需要仔细考虑。
在这个特殊情况下虽然最好是&#34;跳过&#34;失败的报告可能会将它们移动到错误队列并在以后重新处理它们。只要预期会出现这种情况,那么获得部分结果可能比没有任何结果更好:
foreach(var task in tasks)
{
try
{
var pair=await task;
pair.conf.LastReport=pair.report;
}
catch(Exception exc)
{
//Make sure the error is logged
Log.Error(exc);
ErrorQueue.Enqueue(new ProcessingError(conf,ex);
}
}
//Handle errors after the loop
编辑2 - 数据流
为了完整起见,我做每天都有几千张票据报告,而每个GDS电话(每个旅行社出售门票的服务)都需要相当长的时间。我无法同时运行所有请求 - 如果我尝试超过10个并发请求,我就会开始收到服务器序列化错误。我也无法重试一切。
在这种情况下,我使用了TPL DataFlow和一些Railway oriented programming tricks。 DOP为8的ActionBlock处理故障单请求。结果包含在Success
类中并发送到下一个块。失败的请求和异常包含在Failure
类中并发送到另一个块。这两个类都继承自IFlowEnvelope,它具有Successful
标志。是的,这是F#Disriminated Union羡慕的对象。
这与超时等的一些重试逻辑相结合。
在伪代码中,管道如下所示:
var reportingBlock=new TransformBlock<Ticket,IFlowEnvelope<TicketReport>(reportFunc,dopOptions);
var happyBlock = new ActionBlock<IFlowEnvelope<TicketReport>>(storeToDb);
var errorBlock = new ActionBlock<IFlowEnvelope<TicketReport>>(logError);
reportingBlock.LinkTo(happyBlock,linkOptions,msg=>msg.Success);
reportingBlock.LinkTo(errorBlock,linkOptions,msg=>!msg.Success);
foreach(var ticket in tickets)
{
reportingBlock.Post(ticket);
}
reportFunc
捕获任何异常并将其包装为Failure<T>
个对象:
async Task<IFlowEnvelope<Ticket,TicketReport>> reportFunc(Ticket ticket)
{
try
{
//Do the heavy processing
return new Success<TicketReport>(report);
}
catch(Exception exc)
{
//Construct an error message, msg
return new Failure<TicketReport>(report,msg);
}
}
真正的管道包括解析每日报告和个人门票的步骤。每次调用GDS需要1-6秒,因此管道的复杂性是合理的。
答案 1 :(得分:1)
我认为你不需要列表或词典。为什么不使用结果
更新LastReport
的简单循环
foreach (var configuration in MachineConfigurations)
{
configuration.LastReport = await machineService.GetReports(configuration);
}
“并行”执行所有报告
Func<Configuration, Task> loadReport =
async config => config.LastReport = await machineService.GetReports(config);
await Task.WhenAll(MachineConfigurations.Select(loadReport));
非常差的人试图变得更有功能。
Func<Configuration, Task<Configuration>> getConfigWithReportAsync =
async config =>
{
var report = await machineService.GetReports(config);
return new Configuration
{
Id = config.Id,
LastReport = report
};
}
var configsWithUpdatedReports =
await Task.WhenAll(MachineConfigurations.Select(getConfigWithReportAsync));
答案 2 :(得分:-1)
using System.Linq;
var taskResultsWithConfiguration = MachineConfigurations.Select(conf =>
new { Conf = conf, Task = machineService.GetReports(conf) }).ToList();
await Task.WhenAll(taskResultsWithConfiguration.Select(pair => pair.Task));
foreach (var pair in taskResultsWithConfiguration)
pair.Conf.LastReport = pair.Task.Result;