我正在尝试并行运行多个Web服务并将其结果收集到一个容器中。我很难搞清楚正确的语法。
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;
}
答案 0 :(得分:2)
一种方法是:
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();
}
答案 1 :(得分:2)
这是一个更有效的解决方案,不需要锁定(ConcurrentBag
是不必要的):
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>();
while (mappedTasks.Count > 0)
{
var finishedTask = await Task.WhenAny(mappedTasks);
Rate[] rates = await finishedTask;
reducedResults.AddRange(rates);
mappedTasks.Remove(finishedTask);
}
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();
}
基于Start Multiple Async Tasks and Process Them As They Complete样本。