c# - ¿Cómo puedo recopilar los valores devueltos de Parallel.ForEach?
c#-4.0 (5)
Esto parece seguro, rápido y simple:
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;
}
Estoy llamando 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
A continuación se muestra un programa de ejemplo (en su mente, imagine un servicio web lento en lugar de una concatenación de cadenas)
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;
}
}
}
Lo has descartado aquí.
ParallelLoopResult result = Parallel.ForEach(words, word => AddB(word));
Probablemente quieras algo como,
ParallelLoopResult result = Parallel.ForEach(words, word =>
{
string result = AddB(word);
// do something with result
});
Si desea algún tipo de colección al final de este, considere usar una de las colecciones en System.Collections.Concurrent
, como ConcurrentBag
var resultCollection = new ConcurrentBag<string>();
ParallelLoopResult result = Parallel.ForEach(words, word =>
{
resultCollectin.Add(AddB(word));
});
// Do something with result
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
Puede considerar utilizar el método de extensión AsParallel
de IEnumerable
, se ocupará de la concurrencia y recopilará los resultados.
words.AsParallel().Select(AddB).ToArray()
La sincronización (por ejemplo, bloqueos o colecciones concurrentes que utilizan bloqueos) suele ser un cuello de botella de algoritmos concurrentes. Lo mejor es evitar la sincronización tanto como sea posible. Supongo que AsParallel
utiliza algo más inteligente como colocar todos los elementos producidos en un solo hilo en una colección local no concurrente y luego combinarlos al final.
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 los objetos concurrentes no son necesarios a menos que necesite los resultados para interactuar entre sí (como si estuviera calculando una suma o combinando 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.