c# - progressbar - Engendrar hilos mĂșltiples para el trabajo luego esperar hasta que todo haya terminado
backgroundworker c# progressbar (12)
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 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.
- ¿El método suena lógico para acelerar el tiempo de inicio de nuestra aplicación?
- ¿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
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.");
}
}
Mi preferencia para esto es manejar esto a través de un solo WaitHandle, y usar Interlocked para evitar el bloqueo 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 bloqueo.
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());
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/
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.
Si no está en .NET 4.0, puede usar una Lista < ManualResetEvent >, una para cada subproceso y Wait a que se Wait . Para esperar en varios hilos, podría considerar usar WaitAll pero WaitAll 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()
});
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();
}
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.");
}
}
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.
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!!!");
}
}