并行运行异步方法

Dav*_*New 17 c# task-parallel-library async-await

我有一个异步方法,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 };
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

// 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 };
}
Run Code Online (Sandbox Code Playgroud)

我甚至尝试过在任务中和周围等待和异步,但它真的让人感到困惑,我没有运气.

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

Dav*_*ine 23

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

是的,"最佳"方法是利用该Task.WhenAll方法.但是,您的第二种方法应该并行运行.我已经创建了一个.NET Fiddle,这应该有助于解决问题.你的第二种方法实际上应该并行运行.我的小提琴证明了这一点!

考虑以下:

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

    return Task.WhenAll(first, second);
}
Run Code Online (Sandbox Code Playgroud)

注意

最好是使用"异步"后缀,而不是GetThingsGetExpensiveThing-我们应该有GetThingsAsyncGetExpensiveThingAsync分别- .

  • `await Task.WhenAll` 会返回 `Thing[]`,所以不需要 `Result`(事实上,`Result` 会包装异常;你应该使用 `await` 或使用 `await Task.WhenAll` 的结果)。 (2认同)
  • 很好听,谢谢 - 现在还早,我一直在"等待"我的咖啡.:) (2认同)

Hou*_*Cat 8

Task.WhenAll() 在同时执行大量/大量任务时,往往会表现不佳-不会适度/节流。

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

我已经修改了Stephen Toub的博客对现代LINQ的优雅处理方法:

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)));
}
Run Code Online (Sandbox Code Playgroud)

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

扩展用法:

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

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