并行运行异步方法

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

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

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

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

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

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

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

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

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

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

考虑以下:

 public Task GetThingsAsync() { var first = GetExpensiveThingAsync(); var second = GetExpensiveThingAsync(); return Task.WhenAll(first, second); } 

注意

最好使用“Async”后缀,而不是GetThingsGetExpensiveThing – 我们应该分别使用GetThingsAsyncGetExpensiveThingAsync – source 。

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

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

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

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

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

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

请在此处查看此问题以供参考

Task.WhenAll()倾向于在大规模/大量任务同时发射时变得无法执行 – 没有调节/限制。

如果你在列表中做了很多任务并且想要等待最终结果,那么我建议使用一个对并行度有限制的partition

我修改了Stephen Toub的博客优雅的现代LINQ方法:

 public static Task ParallelForEachAsync(this IEnumerable source, Func funcBody, int maxDoP = 4) { async Task AwaitPartition(IEnumerator 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);