C# 如何执行多个任务并收集它们的输出?

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 =&

我正在尝试并行运行多个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;
}
一种方法是:

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上引发异常。你如何处理它取决于你自己,取决于发生了什么。。。我已经为异常处理添加了一个片段,我可能不需要它,但是如果所有的运营商都抛出异常,我想一次将其返回给调用方。