2016-09-30 145 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