2012-09-26 14 views
34

Llamo a un servicio web lento en paralelo. Las cosas fueron geniales hasta que me di cuenta de que necesitaba obtener algo de información del servicio. Pero no veo dónde recuperar los valores. No puedo escribir en la base de datos, HttpContext.Current parece ser nulo dentro de un método llamado usando Parallel.ForEach¿Cómo puedo recopilar valores devueltos de Parallel.ForEach?

A continuación se muestra un programa de ejemplo (en su mente, imagine un servicio web lento en lugar de una cadena de concatenación)

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

una sobrecarga diferente de 'Parallel.ForEach' puede ser lo que quieren: http://msdn.microsoft.com/en-us/library/ dd991486.aspx –

+0

Desafortunadamente eso no es algo que puedas hacer de esa manera. 'Parallel.Foreach()' simplemente no fue creado para hacer un seguimiento de las devoluciones. Sin embargo, sugiero usar parámetros 'ref' en tu función' AddB'. Eso podría hacerlo. –

+0

@PhillipSchmidt: No con la sobrecarga utilizada en el ejemplo de todos modos ... –

Respuesta

44

Lo ha descartado aquí.

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

Es posible que desee algo así,

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

Si desea algún tipo de recogida al final de esto, considerar el uso de una de las colecciones bajo System.Collections.Concurrent, como ConcurrentBag

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

// Do something with result 
+28

Creo que ParallelLoopResult no hace nada útil aquí. +1 a – usr

+1

.AsParallel() en LINQ sería mucho mejor –

+0

¿Es seguro para subprocesos agregar a una lista de varios subprocesos? –

11

No use ConcurrentBag para recopilar resultados, ya que es extremadamente lento. Use el bloqueo local en su lugar.

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

¿Tiene alguna estadística para mostrar que ConcurrentBag es más lento que usar nuestro propio bloqueo de objetos? Solo quiero saber qué tan lento es, ya que hace que mi código se vea más limpio que usar el bloqueo de objetos. –

+0

@dineshygv La diferencia en mi humilde opinión es insignificante http://stackoverflow.com/questions/2950955/concurrentbagof-mytype-vs-listof-mytype/34016915#34016915 –

+0

O no use ningún tipo de bloqueo ;-) – Steves

2

¿Qué tal algo como esto:

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

creo que el bloqueo o de objetos concurrentes no es necesario a menos que necesite los resultados de interactuar unos con otros (como si estuviera calculando una suma o combinación todas las palabras). En este caso, ForEach divide cuidadosamente su lista original y le entrega a cada hilo su propio objeto para que pueda manipular todo lo que quiera sin preocuparse de interferir con los otros hilos.

+0

Sí, eso sería Trabajar para aplicaciones de la Consola, pero el evento para las aplicaciones de la Consola es posible que desee agregarlas primero en una colección, o de lo contrario obtendrá resultados intercalados en la ventana de la Consola. – MatthewMartin

+0

Los comandos Console.WriteLine se ejecutan de forma síncrona en el hilo principal e imprimirá los resultados en el orden en que se definieron en la Lista original después de que Parallel.ForEach termine de procesar todos los elementos de la lista y los devuelva. Si estuviera llamando a WriteLine desde el Parallel.ForEach, entonces sí, los resultados estarían intercalados. – MichaC

11

Puede considerar el uso del método de extensión AsParallel de IEnumerable, se ocupará de la concurrencia y recopilará los resultados.

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

de sincronización (por ejemplo, cerraduras o colecciones concurrentes que utilizan cerraduras) son generalmente cuello de botella de algoritmos concurrentes. Lo mejor es evitar la sincronización tanto como sea posible. Supongo que AsParallel usa algo más inteligente como colocar todos los elementos producidos en un hilo en una colección local no concurrente y luego combinarlos al final.

+1

Esto es significativamente mejor. –

1

Esto parece segura, rápida y sencilla:

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

Es probable que cause un ping-pong de caché, aunque sigue siendo sustancialmente mejor que la recopilación simultánea. – Steves

Cuestiones relacionadas