2016-09-30 119 views
4

我已经切换到.net Core的某些项目,并且现在遇到了Parallel.ForEach的问题。在过去,我经常拥有一个id值列表,然后我将使用它来发出Web请求以获取完整数据。这将是这个样子:.net Core Parallel.ForEach问题

Parallel.ForEach(myList, l => 
{ 
    // make web request using l.id 
    // process the data somehow 
}); 

那么,在.NET核心的Web请求都必须标记await这意味着Parallel.ForEach行动必须标记async。但是,将Parallel.ForEach标记为async意味着我们有一个导致问题的方法void async。在我的特殊情况下,这意味着响应返回之前的并行循环中的所有Web请求都已完成,这既困难又会导致错误。

问:在这里使用Parallel.ForEach有什么替代方法?

一个可能的解决方案,我发现是包装一个任务内的并行循环,等待任务:

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变得虚拟无用(至少在嵌套网络​​调用时)。

任何协助/建议表示赞赏。

+7

'async/await'设计用于长时间和阻塞** I/O操作**,而'Parallel'则用于长时间阻塞** CPU操作**。如果你发现自己试图在一个'Parallel'函数体内编写异步代码,那么你做错了什么。考虑使用[Task.WhenAll](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.whenall(v = vs.110).aspx)。 –

+1

除了上面的评论,当你做Task.Run(async()=> ...)时,你也几乎总是做错了什么。 – Evk

+0

你应该看看[TPL Dataflow](https://msdn.microsoft.com/en-us/library/hh228603(v = vs.110).aspx)。让你的生活变得更轻松。它不是.NET Framework的一部分,但你可以使用nuget来获取它, – ThePerplexedOne

回答

3

这3个应用程序都不错。

在这种情况下,您不应该使用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); 
+0

谢谢你。我能够在我的项目中成功实施它。 – nurdyguy

+0

@Matias你可以发布你的实现代码PLZ吗? – Skadoosh

11

为什么这个任务是在评论解释Parallel.ForEach不好:它是专为CPU绑定(CPU密集型)任务。如果您将它用于IO绑定操作(如发出Web请求) - 您将在等待响应时浪费线程池线程,因为没有好处。可以继续使用它,但对于这种情况并不是最好的。

您需要的是使用异步Web请求方法(如HttpWerRequest.GetResponseAsync),但是这里出现了另一个问题 - 您不希望一次执行所有Web请求(如另一个答案所示)。您的列表中可能会有数千个网址(ids)。所以你可以使用为此设计的线程同步结构,例如SemaphoreSemaphore就像队列 - 它允许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(); 
     } 
    } 
+0

谢谢你。我不认为这是我们现在要去的方式,但是对于未来我们应该牢记这一点。 – nurdyguy