C# 如何执行多个任务并收集它们的输出?
我正在尝试并行运行多个Web服务,并将其结果收集到单个容器中。我很难想出正确的语法C# 如何执行多个任务并收集它们的输出?,c#,.net,asynchronous,task-parallel-library,C#,.net,Asynchronous,Task Parallel Library,我正在尝试并行运行多个Web服务,并将其结果收集到单个容器中。我很难想出正确的语法 public async Task<IEnumerable<Rate>> GetRates(Address originAddress, Address destinationAddress,Package package) { var rates = new List<Rate>(); var tasks = Carriers.Where(carrier =&
public async Task<IEnumerable<Rate>> GetRates(Address originAddress, Address destinationAddress,Package package)
{
var rates = new List<Rate>();
var tasks = Carriers.Where(carrier => carrier.Enabled)
.Select(async carrier =>
{
try
{
rates = await carrier.GetRates(originAddress, destinationAddress, package);
}
finally
{
}
});
await Task.WhenAll(tasks).ConfigureAwait(false);
return rates;
}
一种方法是:
public async Task<IEnumerable<Rate>> GetRates(Address originAddress, Address destinationAddress,Package package)
{
var rates = new List<Rate>();
var tasks = Carriers.Where(carrier => carrier.Enabled)
.Select(async carrier =>
{
try
{
return carrier.GetRates(originAddress, destinationAddress, package);
}
finally
{
}
});
await Task.WhenAll(tasks).ConfigureAwait(false);
foreach (var item in tasks)
{
rates.AddRange(item.Result);
}
return rates;
}
另一种方法是将GetRates列表添加到ConcurrentBag。测试两者,看看哪一个对你来说更快-
public async Task<IEnumerable<string>> GetRates()
{
var rates = new ConcurrentBag<Rate>();
var tasks = rates.Where(carrier => carrier.Enabled)
.Select(async carrier =>
{
try
{
await Task.Run(async () =>
{
var t = await Task.Run(() => carrier.GetRates...
foreach (var item in t)
{
rates.Add(item);
}
});
}
finally
{
}
});
await Task.WhenAll(tasks).ConfigureAwait(false);
return rates;
}
还有另一个解决方案,基于felix-b的解决方案。我非常喜欢它,所以我决定把它作为一种扩展方法。扩展如下所示:
public static IEnumerable<Task<T>> AsItCompletes<T>(this IEnumerable<Task<T>> taskList)
{
var tasks = taskList.ToList();
var sources = tasks.Select(x => new TaskCompletionSource<T>()).ToList();
int currentIndex = -1;
foreach (var task in tasks)
{
task.ContinueWith(completed =>
{
var next = sources[Interlocked.Increment(ref currentIndex)];
if (completed.IsFaulted)
{
next.SetException(completed.Exception);
}
else if (completed.IsCanceled)
{
next.SetCanceled();
}
else
{
next.SetResult(completed.Result);
}
}, TaskContinuationOptions.ExecuteSynchronously);
}
return sources.Select(source => source.Task);
}
生成的代码更加紧凑,易于阅读,无需重复等待,无需删除:
public async Task<IEnumerable<Rate>> GetRates(
Address originAddress, Address destinationAddress, Package package)
{
List<Task<Rate[]>> mappedTasks = Carriers
.Where(c => c.Enabled)
.Select(carrier => ProcessOneCarrier(
carrier, originAddress, destinationAddress, package))
.ToList();
List<Rate> reducedResults = new List<Rate>();
foreach (var task in mappedTasks.AsItCompletes())
{
Rate[] rates = await task;
if (task.Exception != null)
{
// Handle Exception
}
reducedResults.AddRange(rates);
}
return reducedResults;
}
async Task<Rate[]> ProcessOneCarrier(
Carrier carrier, Address originAddress, Address destinationAddress, Package package)
{
var rates = await carrier.GetRates(originAddress, destinationAddress, package);
return rates.ToArray();
}
这里有一个更高效的解决方案,它不需要锁定ConcurrentBag: 公共异步任务获取速率 地址来源地址,地址目的地址,包裹 { 列表mappedTasks=Carriers .其中c=>c.已启用 .Selectcarrier=>ProcessOneCarrier 承运人、原始地址、目的地地址、包裹 托利斯特先生; List reducedResults=新列表; 而mappedTasks.Count>0 { var finishedTask=wait Task.WhenAnymappedTasks; 速率[]速率=等待完成任务; 减少结果。增加范围率; mappedTasks.RemovefinishedTask; } 返回减少的结果; } 异步任务ProcessOneCarrier 承运商承运商,地址来源地址,地址目的地址,包裹 { var rates=等待承运人。GetRate或原始地址、目的地址、包裹; 返回率。ToArray; }
基于样本。使用rates.AddRangeawait carrier.GetRates…..是的。更好的解决方案伟大的答案!不过我有一个问题。随着每个任务的完成,我希望捕获任何异常并继续执行其他任务。这仅仅是一个用try…catch块包围foreach块的问题吗?SetException有点神秘。等待后,检查task.Exception是否有值。我应该把它添加到示例中。类似于if task.Exception!=null do something,因此我将创建一个AggregateAception,并向其中添加任何任务异常。如果AggregateException在任务完成时有异常,那么抛出它?我认为您根本不需要AggregateException。如果任务失败,将在task.exception上引发异常。你如何处理它取决于你自己,取决于发生了什么。。。我已经为异常处理添加了一个片段,我可能不需要它,但是如果所有的运营商都抛出异常,我想一次将其返回给调用方。