Engendrar hilos múltiples para el trabajo luego esperar hasta que todo haya terminado

Solo quiero algunos consejos sobre “mejores prácticas” con respecto a las tareas de subprocesos múltiples.

como ejemplo, tenemos una aplicación C # que al inicio lee datos de varias tablas de “tipo” en nuestra base de datos y almacena la información en una colección que pasamos por la aplicación. esto nos impide golpear la base de datos cada vez que se requiere esta información.

en este momento, la aplicación está leyendo datos de 10 tablas de forma síncrona. Realmente me gustaría tener la aplicación leída de cada tabla en un hilo diferente, todo funcionando en paralelo. la aplicación esperará a que se completen todos los subprocesos antes de continuar con el inicio de la aplicación.

He investigado BackGroundWorker pero solo quiero algunos consejos para lograr lo anterior.

  1. ¿El método suena lógico para acelerar el tiempo de inicio de nuestra aplicación?
  2. ¿Cómo podemos manejar mejor todos los hilos teniendo en cuenta que el trabajo de cada hilo es independiente el uno del otro, solo tenemos que esperar a que todos los hilos se completen antes de continuar?

espero algunas respuestas

Mi preferencia para esto es manejar esto a través de un solo WaitHandle, y usar Interlocked para evitar el locking en un contador:

class Program { static void Main(string[] args) { int numThreads = 10; ManualResetEvent resetEvent = new ManualResetEvent(false); int toProcess = numThreads; // Start workers. for (int i = 0; i < numThreads; i++) { new Thread(delegate() { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // If we're the last thread, signal if (Interlocked.Decrement(ref toProcess) == 0) resetEvent.Set(); }).Start(); } // Wait for workers. resetEvent.WaitOne(); Console.WriteLine("Finished."); } } 

Esto funciona bien y escala a cualquier número de procesos de subprocesos, sin introducir el locking.

Me gusta la solución de @ Reed. Otra forma de lograr lo mismo en .NET 4.0 sería usar CountdownEvent .

 class Program { static void Main(string[] args) { var numThreads = 10; var countdownEvent = new CountdownEvent(numThreads); // Start workers. for (var i = 0; i < numThreads; i++) { new Thread(delegate() { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // Signal the CountdownEvent. countdownEvent.Signal(); }).Start(); } // Wait for workers. countdownEvent.Wait(); Console.WriteLine("Finished."); } } 

Si tiene más de 64 controladores de espera para un hilo STA como dice Mark. podría crear una lista con sus hilos y esperar a que todo se complete en un segundo ciclo.

 //check that all threads have completed. foreach (Thread thread in threadList) { thread.Join(); } 

Si no está en .NET 4.0, puede usar una Lista < ManualResetEvent >, una para cada subproceso y Espere a que se configuren . Para esperar en varios hilos, podría considerar usar WaitAll pero tenga cuidado con el límite de 64 identificadores de espera. Si necesita más que esto, puede recorrerlos y esperar cada uno individualmente.

Si desea una experiencia de inicio más rápida, probablemente no necesite esperar a que se lean todos los datos durante el inicio. Simplemente muestre la GUI y cualquier información que falta se puede mostrar atenuada con algún tipo de ícono de “Actualización …” o similar. Cuando ingrese la información, solo active un evento para actualizar la GUI. Puede haber muchas operaciones que el usuario puede comenzar a realizar incluso antes de que se lean todos los datos de todas las tablas.

Si te sientes aventurero, puedes usar C # 4.0 y la Biblioteca de tareas paralelas:

 Parallel.ForEach(jobList, curJob => { curJob.Process() }); 

Aquí hay dos patrones para esperar en múltiples operaciones paralelas. El truco es que debes tratar tu hilo principal como si fuera una de las operaciones paralelas también. De lo contrario, existe una condición de carrera sutil entre la señalización de finalización en los hilos de trabajo y la espera de esa señal del hilo principal.

 int threadCount = 1; ManualResetEvent finished = new ManualResetEvent(false); for (int i = 0; i < NUM_WORK_ITEMS; i++) { Interlocked.Increment(ref threadCount); ThreadPool.QueueUserWorkItem(delegate { try { // do work } finally { if (Interlocked.Decrement(ref threadCount) == 0) finished.Set(); } }); } if (Interlocked.Decrement(ref threadCount) == 0) finished.Set(); finished.WaitOne(); 

Como preferencia personal, me gusta usar la clase CountdownEvent para hacer el conteo por mí, que está disponible en .NET 4.0.

 var finished = new CountdownEvent(1); for (int i = 0; i < NUM_WORK_ITEMS; i++) { finished.AddCount(); ThreadPool.QueueUserWorkItem(delegate { try { // do work } finally { finished.Signal(); } }); } finished.Signal(); finished.Wait(); 

Los ejemplos anteriores usan ThreadPool , pero puedes ThreadPool por el mecanismo de enhebrado que prefieras.

Solo por diversión, lo que @Reed ha hecho, con Monitor. :PAG

 class Program { static void Main(string[] args) { int numThreads = 10; int toProcess = numThreads; object syncRoot = new object(); // Start workers. for (int i = 0; i < numThreads; i++) { new Thread(delegate() { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); // If we're the last thread, signal if (Interlocked.Decrement(ref toProcess) == 0) { lock (syncRoot) { Monitor.Pulse(syncRoot); } } }).Start(); } // Wait for workers. lock (syncRoot) { if (toProcess > 0) { Monitor.Wait(syncRoot); } } Console.WriteLine("Finished."); } } 

Otra posibilidad con TPL , asumir jobs es la colección de elementos para procesar, o subprocesos para ejecutar:

 Task.WaitAll(jobs .Select(job => TaskFactory.StartNew(() => /*run job*/)) .ToArray()); 

Suponiendo que los hilos del lector de la base de datos regresen tan pronto como terminen, simplemente puede llamar a Thread.Join en los diez hilos sucesivamente desde el hilo de inicio.

Si está utilizando .NET 3.5 o posterior, puede usar una matriz de AsyncResult o BackgroundWorker y contar cuántos hilos han regresado (simplemente no se olvide de disminuir el contador con las operaciones entrelazadas) (consulte http://www.albahari.com / threading / como referencia).

Si está utilizando .NET 4.0, un paralelo para es el enfoque más simple.

un método más fácil que me gusta usar:

  private int ThreadsCount = 100; //initialize threads count private void button1_Click(object sender, EventArgs e) { for (int i = 0; i < ThreadsCount; i++) { Thread t = new Thread(new ThreadStart(myMethod)); t.IsBackground = true; t.Start(); } } private void myMethod() { //everytime a thread finishes executing decrease the threads count ThreadsCount = ThreadsCount - 1; if (ThreadsCount < 1) { //if all threads finished executing do whatever you wanna do here.. MessageBox.Show("Finished Executing all threads!!!"); } } 

Publicando para tal vez ayudar a otros, pasó bastante tiempo buscando una solución como la que se me ocurrió. Así que tomé un enfoque diferente. Estaba generando numerosos hilos e incrementé un contador y decrementé un contador cuando un hilo comenzaba y se detenía. Luego, en el método principal, quería detenerme y esperar a que terminaran los hilos, lo hice.

 while (threadCounter > 0) { Thread.Sleep(500); //Make it pause for half second so that we don't spin the cpu out of control. } 

Documentado en mi blog http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/