并行运行异步方法

时间:2016-07-28 10:57:33

标签: c# async-await task-parallel-library

我有一个异步方法GetExpensiveThing(),它执行一些昂贵的I / O工作。这就是我使用它的方式:

// Serial execution
public async Task<List<Thing>> GetThings()
{
    var first = await GetExpensiveThing();
    var second = await GetExpensiveThing();
    return new List<Thing>() { first, second };
}

但由于它是一种昂贵的方法,我想并行执行这些调用。我本以为移动等待会解决这个问题:

// Serial execution
public async Task<List<Thing>> GetThings()
{
    var first = GetExpensiveThing();
    var second = GetExpensiveThing();
    return new List<Thing>() { await first, await second };
}

这没有用,所以我将它们包装在一些任务中,这有效:

// Parallel execution
public async Task<List<Thing>> GetThings()
{
    var first = Task.Run(() =>
    {
        return GetExpensiveThing();
    });

    var second = Task.Run(() =>
    {
        return GetExpensiveThing();
    });

    return new List<Thing>() { first.Result, second.Result };
}

我甚至尝试在任务中和周围进行等待和异步,但它确实令人困惑,我没有运气。

有没有更好的并行运行异步方法,或者任务是一个好方法?

4 个答案:

答案 0 :(得分:16)

  

是否有更好的并行运行异步方法,或者任务是一种好方法吗?

是的,&#34;最好的&#34;方法是使用Task.WhenAll方法。但是,您的第二种方法应该并行运行。我创建了一个.NET Fiddle,这应该有助于解决问题。你的第二种方法实际上应该并行运行。我的小提琴证明了这一点!

请考虑以下事项:

public Task<Thing[]> GetThingsAsync()
{
    var first = GetExpensiveThingAsync();
    var second = GetExpensiveThingAsync();

    return Task.WhenAll(first, second);
}

注意

最好使用&#34; Async&#34;后缀,而不是GetThingsGetExpensiveThing - 我们应该分别GetThingsAsyncGetExpensiveThingAsync - source

答案 1 :(得分:5)

Task.WhenAll()倾向于在同时执行大规模/大量任务时表现不佳-没有节制/节流。

如果您要在列表中执行很多任务并希望等待最终结果,那么我建议使用partition,并限制并行度。

我对现代LINQ修改了Stephen Toub's blog优雅的方法:

public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> funcBody, int maxDoP = 4)
{
    async Task AwaitPartition(IEnumerator<T> partition)
    {
        using (partition)
        {
            while (partition.MoveNext())
            { await funcBody(partition.Current); }
        }
    }

    return Task.WhenAll(
        Partitioner
            .Create(source)
            .GetPartitions(maxDoP)
            .AsParallel()
            .Select(p => AwaitPartition(p)));
}

它的工作原理很简单,采用IEnumerable-将其分解为均匀的分区,并针对每个分区中的每个元素同时触发函数/方法。任何时候每个分区中的元素不得超过一个,但是n个分区中的n个任务。

扩展用法:

await myList.ParallelForEachAsync(myFunc, Environment.ProcessorCount);

编辑: 现在,如果您需要更多选择,我会在Github的repository中保留一些重载。 NetStandard也在NuGet中。

答案 2 :(得分:3)

如果GetExpensiveThing 正确异步(意味着它没有同步执行任何IO或CPU),那么调用这两种方法然后等待结果的第二种解决方案应该是&# 39;工作过。您也可以使用Task.WhenAll

但是,如果不是这样,您可以通过将每个任务发布到线程池并使用Task.WhenAll组合器来获得更好的结果,例如:

public Task<IList<Thing>> GetThings() =>
    Task.WhenAll(Task.Run(() => GetExpensiveThing()), Task.Run(() => GetExpensiveThing()));

(注意我将返回类型更改为IList以完全避免await。)

您应该避免使用Result属性。它会导致调用者线程阻塞并等待任务完成,这与使用continuation的awaitTask.WhenAll不同。

答案 3 :(得分:2)

你可以使用Task.WhenAll,它在完成所有依赖任务时返回

请检查此问题here以供参考