我有一个异步方法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 };
}
我甚至尝试在任务中和周围进行等待和异步,但它确实令人困惑,我没有运气。
有没有更好的并行运行异步方法,或者任务是一个好方法?
答案 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;后缀,而不是GetThings
和GetExpensiveThing
- 我们应该分别GetThingsAsync
和GetExpensiveThingAsync
- 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的await
或Task.WhenAll
不同。
答案 3 :(得分:2)
你可以使用Task.WhenAll,它在完成所有依赖任务时返回
请检查此问题here以供参考