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?

shaharmor
fuente
¿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.

Haedriano
fuente
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.

Mark Byers
fuente
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;
}
Matas Vaitkevicius
fuente
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.

Matt Mills
fuente
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();
}
Douglas
fuente
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