Parallel.ForEach mit Hinzufügen zur Liste

80

Ich versuche, mehrere Funktionen auszuführen, die eine Verbindung zu einem Remotestandort (über das Netzwerk) herstellen, und eine generische Liste zurückzugeben. Aber ich möchte sie gleichzeitig ausführen.

Zum Beispiel:

public static List<SearchResult> Search(string title)
{
    //Initialize a new temp list to hold all search results
    List<SearchResult> results = new List<SearchResult>();

    //Loop all providers simultaneously
    Parallel.ForEach(Providers, currentProvider =>
    {
        List<SearchResult> tmpResults = currentProvider.SearchTitle((title));

        //Add results from current provider
        results.AddRange(tmpResults);
    });

    //Return all combined results
    return results;
}

Aus meiner Sicht können mehrere Einfügungen in "Ergebnisse" gleichzeitig erfolgen ... Dies kann meine Anwendung zum Absturz bringen.

Wie kann ich das vermeiden?

Shaharmor
quelle
Welche .NET-Version verwenden Sie?
sll
4
Es müsste mindestens .Net 4 sein; Parallel wurde dort eingeführt.
Arootbeer

Antworten:

56
//In the class scope:
Object lockMe = new Object();    

//In the function
lock (lockMe)
{    
     results.AddRange(tmpResults);
}

Grundsätzlich bedeutet eine Sperre, dass nur ein Thread gleichzeitig auf diesen kritischen Abschnitt zugreifen kann.

Haedrian
quelle
1
Aber was passiert, wenn diese Ergebnisse hinzugefügt werden, während die Ergebnisse eines anderen Anbieters hinzugefügt werden sollen? Werden sie scheitern oder warten, bis es möglich ist?
Shaharmor
4
Wenn es eine Sperre gibt, wartet der Thread, bis er die Sperre erhalten kann.
Haedrian
Im Grunde ist es so, als würde man sagen: Warten Sie bis! Results.isLocked, und wenn es frei ist, sperren Sie es und schreiben Sie?
Shaharmor
8
Ein kleiner Punkt: thisist nicht die sicherste Wahl für das Sperrobjekt. Verwenden Sie besser ein spezielles privates Objekt : lock(resultsLock).
Henk Holterman
2
lockskann jedoch die Gesamtausführungszeit verlangsamen. Gleichzeitige Sammlungen scheinen dies besser zu vermeiden
Piotr Kula
155

Sie können eine gleichzeitige Sammlung verwenden .

Der System.Collections.ConcurrentNamespace bietet mehrere thread-sichere Auflistungsklassen, die anstelle der entsprechenden Typen in den Namespaces System.Collections und verwendet werden sollten, System.Collections.Genericwenn mehrere Threads gleichzeitig auf die Auflistung zugreifen.

Sie können zum Beispiel verwenden, ConcurrentBagda Sie keine Garantie haben, in welcher Reihenfolge die Artikel hinzugefügt werden.

Stellt eine thread-sichere, ungeordnete Sammlung von Objekten dar.

Mark Byers
quelle
Ja, das ist die eigentliche Antwort. Mit gleichzeitigen Sammlungen erzielen Sie (im Allgemeinen) eine bessere Leistung.
lkg
32

Für diejenigen, die Code bevorzugen:

public static ConcurrentBag<SearchResult> Search(string title)
{
    var results = new ConcurrentBag<SearchResult>();
    Parallel.ForEach(Providers, currentProvider =>
    {
        results.Add(currentProvider.SearchTitle((title)));
    });

    return results;
}
Matas Vaitkevicius
quelle
Müssen eine Schleife verwenden: foreach (var item in currentProvider.SearchTitle((title))) results.Add(item);
Anthony McGrath
25

Die gleichzeitigen Sammlungen sind neu für .Net 4; Sie sind so konzipiert, dass sie mit der neuen parallelen Funktionalität arbeiten.

Siehe Gleichzeitige Sammlungen in .NET Framework 4 :

Vor .NET 4 mussten Sie Ihre eigenen Synchronisationsmechanismen bereitstellen, wenn mehrere Threads möglicherweise auf eine einzelne gemeinsam genutzte Sammlung zugreifen. Sie mussten die Sammlung sperren ...

... die [neuen] Klassen und Schnittstellen in System.Collections.Concurrent [hinzugefügt in .NET 4] bieten eine konsistente Implementierung für [...] Multithread-Programmierprobleme, bei denen gemeinsame Daten über mehrere Threads hinweg gemeinsam genutzt werden.

Arootbeer
quelle
13

Dies könnte mit PLINQs AsParallelund SelectMany:

public static List<SearchResult> Search(string title)
{
    return Providers.AsParallel()
                    .SelectMany(p => p.SearchTitle(title))
                    .ToList();
}
Douglas
quelle
linq selectMany ist großartig, leider ist linq für jeden langsamer als normal. :(
Kugan Kumar
6
Nicht mikrooptimieren. Das OP impliziert, dass eine SearchTitleVerbindung zu einem Remote-Standort hergestellt wird. Die Latenz ist um mehrere Größenordnungen langsamer als die Differenz zwischen LINQ und foreach.
Douglas