2016-09-30 9 views
4

Ich habe für einige Projekte auf .net Core umgeschaltet und habe jetzt ein Problem mit Parallel.ForEach. In der Vergangenheit hatte ich oft eine Liste von ID-Werten, die ich dann verwenden würde, um Web-Anfragen zu stellen, um die vollständigen Daten zu erhalten. Es würde wie folgt aussehen:.net Core Parallel.ForEach Ausgaben

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

Nun, in .net Kern die Webanfragen alle await markiert werden müssen, die die Parallel.ForEach Aktion bedeutet, muss mit async markiert werden. Aber das Markieren einer Parallel.ForEach-Aktion als async bedeutet, dass wir eine void async-Methode haben, die Probleme verursacht. In meinem speziellen Fall bedeutet dies, dass die Antwort zu der Anwendung zurückkehrt, bevor alle Webanforderungen in der parallelen Schleife abgeschlossen sind, was sowohl umständlich ist als auch Fehler verursacht.

Frage: Was sind die Alternativen zur Verwendung von Parallel.ForEach hier?

Eine mögliche Lösung fand ich war die Parallel-Schleife innerhalb einer Aufgabe und wartet auf die Aufgabe zu wickeln:

await Task.Run(() => Parallel.ForEach(myList, l => 
{ 
    // stuff here 
})); 

(hier: Parallel.ForEach vs Task.Run and Task.WhenAll)

Aber, das nicht für mich funktioniert . Wenn ich das benutze, komme ich immer noch zur Anwendung zurück, bevor die Schleife abgeschlossen ist.

Eine weitere Option:

var tasks = new List<Task>(); 
foreach (var l in myList) 
{ 
    tasks.Add(Task.Run(async() => 
    { 
     // stuff here 
    })); 
} 
await Task.WhenAll(tasks); 

Dies scheint zu arbeiten, aber ist, dass die einzige Option? Es scheint, dass der neue .net-Core Parallel.ForEach praktisch nutzlos gemacht hat (zumindest wenn es um verschachtelte Web-Aufrufe geht).

Jede Hilfe/Beratung wird geschätzt.

+7

'async/await' wurde für lange und blockierende ** I/O-Operationen ** entwickelt, während' Parallel' für lange und blockierende ** CPU-Operationen ** entwickelt wurde. Wenn Sie versuchen, asynchronen Code innerhalb eines 'Parallel'-Funktionskörpers zu schreiben, dann machen Sie etwas falsch. Erwägen Sie stattdessen, [Task.WhenAll] (https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.whenall (v = vs.110) .aspx) zu verwenden. –

+1

Zusätzlich zu obigen Kommentar, wenn Sie Task.Run (async() => ...), Sie auch fast immer etwas falsch machen. – Evk

+0

Sie sollten sich [TPL Dataflow] (https://msdn.microsoft.com/en-us/library/hh228603 (v = vs.110) .aspx) ansehen. Macht dein Leben viel einfacher. Es ist kein Teil des .NET Frameworks, aber Sie können nuget verwenden, um es zu bekommen, – ThePerplexedOne

Antwort

3

Keine dieser 3 Entscheidungen sind gut.

Sie sollten die Parallel Klasse oder Task.Run für dieses Szenario nicht verwenden.

Stattdessen haben eine async-Handler-Methode:

private async Task HandleResponse(Task<HttpResponseMessage> gettingResponse) 
{ 
    HttpResponseMessage response = await gettingResponse; 
    // Process the data 
} 

Und dann verwenden Task.WhenAll:

Task[] requests = myList.Select(l => SendWebRequest(l.Id)) 
         .Select(r => HandleResponse(r)) 
         .ToArray(); 

await Task.WhenAll(requests); 
+0

Vielen Dank dafür. Ich konnte es in meinem Projekt erfolgreich umsetzen. – nurdyguy

+0

@Matias können Sie Ihren implementierten Code plz posten? – Skadoosh

11

Warum Parallel.ForEach ist nicht gut für diese Aufgabe in den Kommentaren erklärt: Es ist für CPU-bound fertigt (CPU-intensive) Aufgaben. Wenn Sie es für E/A-gebundene Vorgänge verwenden (wie zum Beispiel Webanfragen), werden Sie Thread-Thread blockiert, während Sie auf eine Antwort warten, für nichts Gutes. Es ist möglich, es immer noch zu verwenden, aber es ist nicht das beste für dieses Szenario.

Was Sie brauchen, ist die Verwendung von asynchronen Web-Request-Methoden (wie HttpWerRequest.GetResponseAsync), aber hier kommt ein weiteres Problem - Sie wollen nicht alle Ihre Web-Anfragen auf einmal ausführen (wie eine andere Antwort vorschlägt). Es kann Tausende URLs (IDs) in Ihrer Liste geben. Sie können also die dafür entworfenen Thread-Synchronisationskonstrukte verwenden, zum Beispiel Semaphore. Semaphore ist wie Warteschlange - es ermöglicht X-Threads zu übergeben, und der Rest sollte warten, bis einer der aktiven Threads seine Arbeit beendet (eine vereinfachte Beschreibung).Hier ein Beispiel:

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

Vielen Dank dafür. Ich glaube nicht, dass wir so vorgehen werden, aber es ist etwas, das man für die Zukunft im Auge behalten sollte. – nurdyguy