2012-09-26 10 views
34

Ich rufe einen langsamen Webservice parallel an. Die Dinge waren großartig, bis ich realisierte, dass ich einige Informationen vom Service zurückbekommen musste. Aber ich sehe nicht, wo ich die Werte zurückbekomme. Ich kann nicht in die Datenbank schreiben, HttpContext.Current scheint Null innerhalb einer Methode namens mit Parallel.ForEachWie sammle ich Rückgabewerte von Parallel.ForEach?

Unten ist ein Beispielprogramm (in Ihrem Kopf, stellen Sie sich bitte einen langsamen Web-Service anstelle einer Zeichenfolge Verkettung)

using System; 
using System.Threading.Tasks; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     WordMaker m = new WordMaker(); 
     m.MakeIt(); 
    } 
    public class WordMaker 
    { 
     public void MakeIt() 
     { 
      string[] words = { "ack", "ook" }; 
      ParallelLoopResult result = Parallel.ForEach(words, word => AddB(word)); 
      Console.WriteLine("Where did my results go?"); 
      Console.ReadKey(); 
     } 
     public string AddB(string word) 
     { 
      return "b" + word; 
     } 
    } 

} 
+0

Eine andere Überlastung von 'Parallel.ForEach' sein kann, was Sie wollen: http://msdn.microsoft.com/en-us/library/ dd991486.aspx –

+0

Leider kann man das nicht wirklich so machen. 'Parallel.Foreach()' wurde einfach nicht dafür entwickelt, die Rendite zu verfolgen. Ich würde jedoch vorschlagen, 'ref' Parameter in Ihrer' AddB' Funktion zu verwenden. Das könnte es tun. –

+0

@PhillipSchmidt: Nicht mit der im Beispiel ohnehin verwendeten Überlast ... –

Antwort

44

Sie haben es hier verworfen.

ParallelLoopResult result = Parallel.ForEach(words, word => AddB(word)); 

Sie wahrscheinlich wollen etwas wie,

ParallelLoopResult result = Parallel.ForEach(words, word => 
{ 
    string result = AddB(word); 
    // do something with result 
}); 

Wenn Sie irgendeine Art von Sammlung am Ende dieses wollen, sollten Sie eine der Sammlungen unter System.Collections.Concurrent verwenden, wie ConcurrentBag

var resultCollection = new ConcurrentBag<string>(); 
ParallelLoopResult result = Parallel.ForEach(words, word => 
{ 
    resultCollectin.Add(AddB(word)); 
}); 

// Do something with result 
+28

Ich denke, das ParallelLoopResult macht hier nichts Sinnvolles. +1 obwohl – usr

+1

.AsParallel() in LINQ wäre viel besser –

+0

Ist es threadsicher, eine Liste aus mehreren Threads hinzuzufügen? –

11

Verwenden Sie nicht ConcurrentBag, um Ergebnisse zu sammeln, da es extrem langsam ist. Verwenden Sie stattdessen die lokale Sperre.

var resultCollection = new List<string>(); 
object localLockObject = new object(); 

Parallel.ForEach<string, List<string>>(
     words, 
    () => { return new List<string>(); }, 
     (word, state, localList) => 
     { 
     localList.Add(AddB(word)); 
     return localList; 
     }, 
     (finalResult) => { lock (localLockObject) resultCollection.AddRange(finalResult); } 
); 

// Do something with resultCollection here 
+0

Haben Sie irgendwelche Statistiken um zu zeigen, dass ConcurrentBag langsamer ist als unsere eigene Objektsperre? Ich möchte nur wissen, wie langsam es ist, da es meinen Code sauberer macht als die Objektsperre. –

+0

@dineshygv IMHO Unterschied ist vernachlässigbar http://stackoverflow.com/questions/2950955/concurrentbagof-mytype-vs-listof-myptype/34016915#34016915 –

+0

Oder verwenden Sie überhaupt keine Verriegelung ;-) – Steves

2

Wie wäre es etwa so:

public class WordContainer 
{ 
    public WordContainer(string word) 
    { 
     Word = word; 
    } 

    public string Word { get; private set; } 
    public string Result { get; set; } 
} 

public class WordMaker 
{ 
    public void MakeIt() 
    { 
     string[] words = { "ack", "ook" }; 
     List<WordContainer> containers = words.Select(w => new WordContainer(w)).ToList(); 

     Parallel.ForEach(containers, AddB); 

     //containers.ForEach(c => Console.WriteLine(c.Result)); 
     foreach (var container in containers) 
     { 
      Console.WriteLine(container.Result); 
     } 

     Console.ReadKey(); 
    } 

    public void AddB(WordContainer container) 
    { 
     container.Result = "b" + container.Word; 
    } 
} 

Ich glaube, die Verriegelung oder gleichzeitige Objekte ist nicht erforderlich, es sei denn, Sie die Ergebnisse müssen miteinander zu interagieren (wie Sie eine Summe wurden die Berechnung oder die Kombination von Alle Wörter). In diesem Fall unterbricht ForEach ordentlich Ihre ursprüngliche Liste und übergibt jedem Thread sein eigenes Objekt, damit es alles manipulieren kann, ohne sich Gedanken über die anderen Threads machen zu müssen.

+0

Ja, das wäre funktionieren für Console-Apps, aber Event für Console-Apps, die Sie möglicherweise zuerst in einer Sammlung aggregieren möchten, oder Sie erhalten verschachtelte Ergebnisse im Konsolenfenster. – MatthewMartin

+0

Die Befehle von Console.WriteLine werden synchron im Hauptthread ausgeführt und die Ergebnisse werden in der Reihenfolge ausgedruckt, in der sie in der ursprünglichen Liste definiert wurden, nachdem Parallel.ForEach alle Listenelemente verarbeitet und zurückgibt. Wenn ich WriteLine innerhalb des Parallel.ForEach aufrufen würde, würden ja die Ergebnisse verschachtelt. – MichaC

11

Sie können die AsParallel Erweiterungsmethode von IEnumerable verwenden, es wird sich um die Nebenläufigkeit für Sie kümmern und die Ergebnisse sammeln.

words.AsParallel().Select(AddB).ToArray()

Synchronization (z.B. Sperren oder gleichzeitige Sammlung, die Sperren verwenden) sind in der Regel gleichzeitiger Engpass-Algorithmen. Am besten ist es, die Synchronisation so weit wie möglich zu vermeiden. Ich nehme an, dass AsParallel etwas intelligenteres verwendet, als würde man alle Elemente, die in einem einzelnen Thread erzeugt wurden, in eine lokale nicht gleichzeitige Sammlung einfügen und diese dann am Ende kombinieren.

+1

Dies ist deutlich besser. –

1

Dies scheint sicher, schnell und einfach:

public string[] MakeIt() { 
     string[] words = { "ack", "ook" }; 
     string[] results = new string[words.Length]; 
     ParallelLoopResult result = 
      Parallel.For(0, words.Length, i => results[i] = AddB(words[i])); 
     return results; 
    } 
+0

Dies verursacht wahrscheinlich Cache-Ping-Pong, obwohl es immer noch wesentlich besser ist als die gleichzeitige Sammlung. – Steves

Verwandte Themen