C# 使用异步/等待并行化多个长时间运行的任务

C# 使用异步/等待并行化多个长时间运行的任务,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我有一个helper方法返回IEnumerable。随着收藏量的增长,它的速度也急剧放缓。我目前的做法基本上是做以下工作: var results = new List<string>(); foreach (var item in items) { results.Add(await item.Fetch()); } And.WhenAll(返回字符串[]): var tasks=items.Select(i=>i.Fetch()); 返回Task.WhenAll(tas

我有一个helper方法返回
IEnumerable
。随着收藏量的增长,它的速度也急剧放缓。我目前的做法基本上是做以下工作:

var results = new List<string>();
foreach (var item in items)
{
    results.Add(await item.Fetch());
}
And.WhenAll(返回字符串[]):

var tasks=items.Select(i=>i.Fetch());
返回Task.WhenAll(tasks.Result);
最后一搏是停止所有长时间运行的作业,然后依次等待它们(希望它们都是并行运行的,所以等待其中一个会让所有其他作业几乎完成):

var tasks=newlinkedlist();
foreach(项目中的var项目)
tasks.AddLast(item.Fetch());
var results=newlinkedlist();
foreach(任务中的var任务)
results.AddLast(task.Result);

在每个测试用例中,运行所需的时间与项目数成正比。这样做没有明显的加速效果。在使用任务和
等待
/
异步
时,我缺少了什么?

并行和并发之间有区别。并发只意味着一次做多件事,而并行意味着在多个线程上做多件事
async
非常适合并发,但不能(直接)帮助您实现并行性

作为一般规则,应避免ASP.NET上的并行性。这是因为您所做的任何并行工作(即,
AsParallel
parallel.ForEach
,等等)都与ASP.NET共享相同的线程池,从而降低了ASP.NET处理其他请求的能力。这会影响web服务的可伸缩性。最好将线程池留给ASP.NET

然而,并发性很好——特别是异步并发。这就是
任务的位置。当所有的
进入时。您应该查找这样的代码(请注意,没有调用Task.Result):

var tasks=items.Select(i=>i.Fetch());
返回等待任务。WhenAll(任务);
考虑到其他代码示例,最好从
Fetch
开始运行调用树,并将所有
Result
调用替换为
wait
。这可能是问题的一部分,因为
Result
强制同步执行


另一个可能的问题是,正在获取的底层资源不支持并发访问,或者可能存在您不知道的限制。例如,如果
Fetch
从另一个web服务检索数据,请查看
System.Net.ServicePointManager.DefaultConnectionLimit

Async不会给您带来算法性能改进的好处,相反,它将允许您的主线程(UI)在工作完成时保持响应。如果任务CPU受限,则应看到与CPU数量成比例的加速,除非您的代码有某些内容(如共享
锁定下的大块代码)这完全阻止了它并行运行,或者由于其他处理,CPU已经达到100%。@JohnKoerner:
.AsParallel
不会放弃这些异步任务的加速吗?我最初开始考虑将我的
foreach
调整为一个
并行的.foreach
,然后我进入了一个异步/等待的世界。是的,
AsParallel
将允许您使用更多线程来完成任务。使用类似于
ConcurrentBag
的工具可能会有所帮助,还可以在发布模式下运行测试,以确定代码的实际性能。
Fetch
到底在做什么?它是执行IO绑定工作还是CPU绑定工作?也许我不是在摸索
async
/
wait
,而是想在调试器中看到结果本身,而不是任务。如果我的整个调用链(一直到控制器的操作)都是
异步的
,那么我是否可以在所有的
(如您的代码示例中)实现
,并在当前的
foreach
实现的基础上提高性能?是的,只要底层资源允许并发访问并且没有限制。
public async Task<IHttpActionResult> FetchAllItems()
var results = items
    .AsParallel()
    .Select(i => i.Fetch().Result)
    .AsList();
return results;
var tasks = items.Select(i => i.Fetch());
return Task<string>.WhenAll<string>(tasks).Result;
var tasks = new LinkedList<Task<string>>();
foreach (var item in items)
    tasks.AddLast(item.Fetch());

var results = new LinkedList<string>();
foreach (var task in tasks)
    results.AddLast(task.Result);
var tasks = items.Select(i => i.Fetch());
return await Task<string>.WhenAll<string>(tasks);