如何执行多个任务并收集其输出?

时间:2017-10-29 17:51:48

标签: 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 => carrier.Enabled)
        .Select(async carrier => 
        {
            try
            {
                rates = await carrier.GetRates(originAddress, destinationAddress, package);
            }
            finally
            {
            }
        });

     await Task.WhenAll(tasks).ConfigureAwait(false);

    return rates;
}

2 个答案:

答案 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样本。