Parallel.ForEach con agregar a la lista


80

Estoy tratando de ejecutar varias funciones que se conectan a un sitio remoto (por red) y devuelven una lista genérica. Pero quiero ejecutarlos simultáneamente.

Por ejemplo:

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;
}

Como lo veo, pueden ocurrir múltiples inserciones en 'resultados' al mismo tiempo ... Lo que puede bloquear mi aplicación.

¿Cómo puedo evitar esto?


¿Qué versión de .NET estás usando?
— hasta el

4
Tendría que ser al menos .Net 4; Paralelo se introdujo allí.
— Matt Mills

Respuestas:


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

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

Básicamente, un bloqueo significa que solo un hilo puede tener acceso a esa sección crítica al mismo tiempo.


1
Pero, ¿qué pasará si MIENTRAS se agregan esos resultados, los resultados de otro proveedor intentan agregarlos? ¿FALLARÁN o ESPERARÁN hasta que sea posible?
— shaharmor

4
Cuando hay un bloqueo, el hilo esperará hasta que pueda obtener el bloqueo.
— Haedrian

Así que, básicamente, es como decir: ¡Espere hasta que! Resultados.está bloqueado, y cuando esté libre, bloquearlo y escribir.
— shaharmor

8
Un punto menor: thisno es la opción más segura para el objeto de bloqueo. Es mejor utilizar un objeto especial privado: lock(resultsLock).
— Henk Holterman

2
locksSin embargo, puede ralentizar el tiempo de ejecución general ... las colecciones concurrentes parecen ser mejores para evitar eso
— Piotr Kula

155

Puede utilizar una colección concurrente .

El System.Collections.Concurrentespacio de nombres proporciona varias clases de colección seguras para subprocesos que deben usarse en lugar de los tipos correspondientes en los espacios de nombres System.Collections y System.Collections.Genericsiempre que varios subprocesos accedan a la colección al mismo tiempo.

Puede utilizar, por ejemplo, ConcurrentBagya que no tiene garantía de qué pedido se añadirán los artículos.

Representa una colección de objetos desordenada y segura para subprocesos.


Sí, esta es la respuesta real. Obtendrá un mejor rendimiento (generalmente) con colecciones simultáneas.
— lkg

32

Para aquellos que prefieren el código:

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

    return results;
}

Tengo que usar un bucle: foreach (var item in currentProvider.SearchTitle((title))) results.Add(item);
— Anthony McGrath

25

Las colecciones concurrentes son nuevas para .Net 4; están diseñados para trabajar con la nueva funcionalidad paralela.

Consulte Colecciones simultáneas en .NET Framework 4 :

Antes de .NET 4, tenía que proporcionar sus propios mecanismos de sincronización si varios subprocesos podían acceder a una única colección compartida. Tenías que bloquear la colección ...

... las [nuevas] clases e interfaces en System.Collections.Concurrent [agregadas en .NET 4] proporcionan una implementación coherente [...] para problemas de programación multiproceso que involucran datos compartidos entre subprocesos.


14

Esto podría expresarse de forma concisa utilizando PLINQ AsParallely SelectMany:

public static List<SearchResult> Search(string title)
{
    return Providers.AsParallel()
                    .SelectMany(p => p.SearchTitle(title))
                    .ToList();
}

linq selectMany es genial, lamentablemente linq es más lento de lo normal para cada uno. :(
— Kugan Kumar

6
No micro-optimice. El OP implicó que se SearchTitleconecta a un sitio remoto. Su latencia será varios órdenes de magnitud más lenta que la diferencia entre LINQ y foreach.
— Douglas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.