我已经切换到某些项目的.net Core,现在我遇到了Parallel.ForEach的问题。在过去,我经常有一个id值列表,然后我将用它来发送Web请求以获取完整数据。它看起来像这样:
Parallel.ForEach(myList, l =>
{
// make web request using l.id
// process the data somehow
});
好吧,在.net Core中,必须标记所有网页请求await
,这意味着必须使用async
标记Parallel.ForEach操作。但是,将Parallel.ForEach操作标记为async
意味着我们有一个导致问题的void async
方法。在我的特殊情况下,这意味着响应返回到应用程序之前并行循环中的所有Web请求都已完成,这既尴尬又会导致错误。
问题:在这里使用Parallel.ForEach有哪些替代方案?
我发现一个可能的解决方案是将Parallel循环包装在Task中并等待任务:
await Task.Run(() => Parallel.ForEach(myList, l =>
{
// stuff here
}));
(在此处找到:Parallel.ForEach vs Task.Run and Task.WhenAll)
但是,这对我不起作用。当我使用它时,我仍然在循环完成之前返回应用程序。
另一种选择:
var tasks = new List<Task>();
foreach (var l in myList)
{
tasks.Add(Task.Run(async () =>
{
// stuff here
}));
}
await Task.WhenAll(tasks);
这似乎有效,但这是唯一的选择吗?似乎新的.net Core已经使Parallel.ForEach几乎无用(至少在嵌套的Web调用时)。
感谢任何帮助/建议。
答案 0 :(得分:16)
注释中解释了为什么Parallel.ForEach
不适合此任务:它专为CPU绑定(CPU密集型)任务而设计。如果你将它用于IO绑定操作(比如发出web请求) - 你会在等待响应时浪费线程池线程,没有任何好处。它仍然可以使用它,但这不适合这种情况。
您需要的是使用异步Web请求方法(如HttpWebRequest.GetResponseAsync),但这里还有另一个问题 - 您不希望立即执行所有Web请求(如另一个答案所示)。您的列表中可能有数千个网址(ID)。因此,您可以使用为此设计的线程同步构造,例如Semaphore
。 Semaphore
就像队列一样 - 它允许X线程通过,其余的应该等到其中一个繁忙线程完成它的工作(稍微简化一下)。这是一个例子:
static async Task ProcessUrls(string[] urls) {
var tasks = new List<Task>();
// semaphore, allow to run 10 tasks in parallel
using (var semaphore = new SemaphoreSlim(10)) {
foreach (var url in urls) {
// await here until there is a room for this task
await semaphore.WaitAsync();
tasks.Add(MakeRequest(semaphore, url));
}
// await for the rest of tasks to complete
await Task.WhenAll(tasks);
}
}
private static async Task MakeRequest(SemaphoreSlim semaphore, string url) {
try {
var request = (HttpWebRequest) WebRequest.Create(url);
using (var response = await request.GetResponseAsync().ConfigureAwait(false)) {
// do something with response
}
}
catch (Exception ex) {
// do something
}
finally {
// don't forget to release
semaphore.Release();
}
}
答案 1 :(得分:9)
这3个apporaches都不好。
您不应在此方案中使用Parallel
类或Task.Run
。
相反,有一个async
处理程序方法:
private async Task HandleResponse(Task<HttpResponseMessage> gettingResponse)
{
HttpResponseMessage response = await gettingResponse;
// Process the data
}
然后使用Task.WhenAll
:
Task[] requests = myList.Select(l => SendWebRequest(l.Id))
.Select(r => HandleResponse(r))
.ToArray();
await Task.WhenAll(requests);
答案 2 :(得分:1)
您应该使用ref关键字调用方法以完成工作,并且应该以最小的努力完成这项工作。在类似情况下,这种方法对我来说效果很好。
Parallel.ForEach(myList, l =>
{
// make web request using ref l.id
string id=l.id;
WebRequest webRequest= MakeRequest(ref id);
// process the data somehow
});
private WebRequest MakeRequest(ref string id)
{
//make and return web request
}
答案 3 :(得分:-1)
我认为此代码将起作用:
for (int i = 0; i < myList.Length; i++)
{
var item = myList[i];
var msg = await SendAsync(item.Id);
//Post Process
}