2012-04-24 20 views
8

Soy un principiante en C#. Tengo un programa que lanzará varios hilos dentro de una aplicación de Windows. Mi objetivo es comenzar un nuevo hilo para cada elemento dentro de una lista. Los elementos en esta lista son nombres de estaciones de trabajo en una red. Cada hilo que se crea buscará hacer reparaciones en cada máquina, cuando el hilo haya terminado escribirá en un archivo de registro cualquier error encontrado, etc. Pero lo que quiero poder determinar es cuándo terminaron todos los hilos. Entonces, si tengo 100 máquinas, 100 hilos, ¿cómo puedo determinar cuándo se han cerrado todos?Determinar cuándo terminaron todos los hilos C#

Heres mi método a continuación: -

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
    if (machineList.Count() != 0) 
    { 
     foreach (string ws in machineList) 
     { 
      new Thread(new ParameterizedThreadStart(fixClient), stack).Start(ws); 
     } 
    } 
    else 
    { 
     MessageBox.Show("Please import data before attempting this procedure"); 
    } 
} 
+0

posible duplicado de [Espere dos hilos para finalizar] (http://stackoverflow.com/questions/594230/wait-for-two-threads-to-finish) –

+1

Hay un buen método para Tareas, 'Tarea. WaitAll' (http://msdn.microsoft.com/en-us/library/dd270695.aspx). Por supuesto, si tiene que usar hilos, eso no ayudará. – phg

+0

También recomiendo que empiece a usar la biblioteca de Tarea paralela. – vtortola

Respuesta

10

La manera de hacer esto sería mantener una referencia a todos los hilos y luego Join en ellos. Esto básicamente significa que el hilo actual se bloqueará hasta que se complete el hilo unido.

Cambiar el bucle a algo como:

foreach (string ws in machineList) 
{ 
    var thread = new Thread(new ParameterizedThreadStart(fixClient), stack); 
    _machineThreads.Add(thread) 
    thread.Start(); 
} 

(donde _machineThreads es una lista de System.Thread)

A continuación, puede bloquear hasta que todos estén completa con algo como:

private void WaitUntilAllThreadsComplete() 
{ 
    foreach (Thread machineThread in _machineThreads) 
    { 
     machineThread.Join(); 
    } 
} 

SIN EMBARGO - casi seguro que no desea hacer esto para el escenario que describe:

  • No debe crear un gran número de hilos - crear explícitamente cientos de hilos, no es una gran idea
  • Usted debe preferir otros enfoques - intente buscar en Parallel.ForEach y System.Threading.Task. En estos días, .Net le brinda mucha ayuda cuando trabaja con tareas de subprocesamiento y tareas asíncronas: realmente le aconsejaría que lo leyera en lugar de tratar de "lanzar el suyo" con hilos explícitos.
  • Parece un controlador de clic. ¿Se trata de formularios web ASP.NET o una aplicación de escritorio? Si el primero, ciertamente no recomendaría generar muchos hilos para realizar tareas en segundo plano a partir de la solicitud. En cualquier caso, ¿realmente quieres bloquear tu página web o GUI mientras esperas que se completen los hilos?
+0

¡Gracias por esto! Absolutley Brilliant! – Derek

+0

¿Hará la diferencia que haya limitado cada hilo a 32kb? Tenía dudas sobre el uso de la memoria que crearía en el servidor, pero, aunque eso limitarlo a 32kb sería suficiente? – Derek

+0

No se trata solo del uso de memoria: la creación de un hilo es costosa, en comparación con el uso de uno del grupo de subprocesos. Además, no sé qué están haciendo tus hilos, pero a menos que estén embotellados en otra parte, tal vez por IO en el disco o en el acceso a la base de datos, entonces todos tratarán de ejecutarse constantemente. Esto significa que la CPU tendrá que pasar mucho tiempo simplemente cambiando entre los hilos en lugar de ejecutarlos. Cambiar los hilos también es costoso. A menudo es más rápido tener una menor cantidad de subprocesos y reutilizarlos, que crear montones de subprocesos y hacer que compitan por el tiempo de CPU. –

4

puede utilizar: IsAlive. Pero hay que mantener una referencia como

Thread t = new Thread(new ThreadStart(...)); 
t.start(); 
if(t.IsAlive) 
{ 
    //do something 
} 
else 
{ 
    //do something else 
} 
1

Se puede crear WaitHandle para cada hilo que está esperando:

WaitHandle[] waitHandles = new WaitHandle[machineList.Count()]; 

Añadir ManualResetEvent a la lista y pasarlo al Tema:

for (int i = 0; i < machineList.Count(); i++) 
{ 
    waitHandles[i] = new ManualResetEvent(false); 
    object[] parameters = new object[] { machineList[i], waitHandles[i] }; 
    new Thread(new ParameterizedThreadStart(fixClient), stack).Start(parameters); 
} 

// wait until each thread will set its manual reset event to signaled state 
EventWaitHandle.WaitAll(waitHandles); 

Dentro de ti método del hilo:

public void fixClient(object state) 
{ 
    object[] parameters = (object[])state; 
    string ws = (string)parameters[0]; 
    EventWaitHandle waitHandle = (EventWaitHandle)parameters[1]; 

    // do your job 

    waitHandle.Set(); 
} 

El hilo principal continuará la ejecución cuando todos los hilos establezcan sus manejadores de espera.

+0

'WaitHandle.WaitAll' no es una mala opción en algunos casos, pero en este caso bloqueará el hilo de UI. Bueno, en realidad para ser completamente enanombrado arrojará una excepción porque tiene un límite de 64 manejadores. –

+0

@BrianGideon gracias, no sabía sobre el límite de cuentas de las manijas! –

2

Otra idea sería usar Parallel.ParaCada en un hilo separado: Esto también es seguro si tiene demasiadas máquinas para fijar

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 

    if (machineList.Count() != 0) 
    { 
     AllFinished=False; 
     new Thread(new ThreadStart(fixAllClients).Start(); 
    } 
    else 
    { 
     MessageBox.Show("Please import data before attempting this procedure"); 
    } 
} 

private void fixAllClients(){ 
    var options = new ParallelOptions{MaxDegreeOfParallelism=10}; 
    Parallel.ForEach(machineList. options, fixClient); 
    AllFinished=True; 
} 
+1

Sin mecanismo de señalización de finalización. ¿Cómo se notificará el hilo principal una vez completado? Necesita invocar/iniciar invocar en lugar de establecer una bandera. –

+0

@MartinJames: Al menos no bloquea el hilo de la interfaz de usuario como muchas de las otras respuestas :) –

2

Nunca esperar a la finalización de rosca, o cualquier otra cosa, en un controlador de eventos GUI. Si genera muchos hilos, (y sí, no haga eso, consulte la publicación de Rob), o envíe muchas tareas a un threadpool, la última entidad para completar la ejecución debe indicar el hilo de la GUI que el trabajo está completo. Por lo general, esto implica llamar a un objeto que cuenta las tareas/hilos y señales restantes cuando se dispara el último. Mire System.Threading.CountdownEvent.

+0

+1 por ser la única persona que señala todas estas sugerencias mediante 'Thread.Join',' WaitHandle.WaitOne' , etc. no funcionará en el hilo de UI. –

2

Hay una forma alternativa de hacer esto que usa la clase CountdownEvent.

El código que inicia los subprocesos debe incrementar un contador y pasar un objeto CountdownEvent a cada subproceso. Cada hilo llamará a CountdownEvent.Signal() cuando haya terminado.

El código siguiente ilustra este enfoque:

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace ConsoleApplication6 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      int numTasks = 20; 
      var rng = new Random(); 

      using (var finishedSignal = new CountdownEvent(1)) 
      { 
       for (int i = 0; i < numTasks; ++i) 
       { 
        finishedSignal.AddCount(); 
        Task.Factory.StartNew(() => task(rng.Next(2000, 5000), finishedSignal)); 
       } 

       // We started with a count of 1 to prevent a race condition. 
       // Now we must decrement that count away by calling .Signal(). 

       finishedSignal.Signal(); 
       Console.WriteLine("Waiting for all tasks to complete..."); 
       finishedSignal.Wait(); 
      } 

      Console.WriteLine("Finished waiting for all tasks to complete."); 
     } 

     static void task(int sleepTime, CountdownEvent finishedSignal) 
     { 
      Console.WriteLine("Task sleeping for " + sleepTime); 
      Thread.Sleep(sleepTime); 
      finishedSignal.Signal(); 
     } 
    } 
} 
+1

+1 para la primera publicación que sugiere una alternativa que podría evitar esperar en un controlador de eventos GUI. Esperar en un controlador de eventos GUI con Join() o alguna otra espera de bloqueo es casi suicida. –

+0

@MartinJames: No, 'CountdownEvent.Wait' no bombea mensajes por lo que también bloquearía el subproceso de la interfaz de usuario. –

+0

Sí, lo correcto sería tener otro hilo que llame a finishedSignal.Wait() y luego use BeginInvoke() para llamar a un método apropiado en el hilo de la interfaz de usuario. –

2

Vamos a conseguir algo fuera del camino primero.

  • No cree hilos individuales para esto. Los hilos son un recurso costoso. En cambio, use técnicas de agrupamiento de subprocesos.
  • No bloquee el hilo de la IU llamando al Thread.Join, WaitHandle.WaitOne, o cualquier otro mecanismo de bloqueo.

Así es como haría esto con el TPL.

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
    if (machineList.Count() != 0) 
    { 
    // Start the parent task. 
    var task = Task.Factory.StartNew(
    () => 
     { 
     foreach (string ws in machineList) 
     { 
      string capture = ws; 
      // Start a new child task and attach it to the parent. 
      Task.Factory.StartNew(
      () => 
      { 
       fixClient(capture); 
      }, TaskCreationOptions.AttachedToParent); 
     } 
     }, TaskCreationOptions.LongRunning); 

    // Define a continuation that happens after everything is done. 
    task.ContinueWith(
     (parent) => 
     { 
     // Code here will execute after the parent task including its children have finished. 
     // You can safely update UI controls here. 
     }, TaskScheduler.FromCurrentSynchronizationContext); 
    } 
    else 
    { 
    MessageBox.Show("Please import data before attempting this procedure"); 
    } 
} 

Lo que estoy haciendo aquí es crear una tarea primaria que en sí misma activará las tareas secundarias. Observe que uso TaskCreationOptions.AttachedToParent para asociar las tareas secundarias con sus padres. Luego, en la tarea principal llamo al ContinueWith, que se ejecuta después de que el padre y todos sus hijos hayan finalizado. Uso TaskScheduler.FromCurrentSynchronizationContext para obtener la continuación en el hilo de la interfaz de usuario.

Y aquí hay una solución alternativa que usa Parallel.ForEach. Tenga en cuenta que es una solución un poco más limpia.

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
    if (machineList.Count() != 0) 
    { 
    // Start the parent task. 
    var task = Task.Factory.StartNew(
    () => 
     { 
     Parallel.Foreach(machineList, 
      ws => 
      { 
      fixClient(ws); 
      }); 
     }, TaskCreationOptions.LongRunning); 

    // Define a continuation that happens after everything is done. 
    task.ContinueWith(
     (parent) => 
     { 
     // Code here will execute after the parent task has finished. 
     // You can safely update UI controls here. 
     }, TaskScheduler.FromCurrentSynchronizationContext); 
    } 
    else 
    { 
    MessageBox.Show("Please import data before attempting this procedure"); 
    } 
} 
+0

Según la pregunta de @Excuseme en polvo para hornear. Hay un error de sintaxis con el uso de ContinueWith(). La acción pasará al delegado de la tarea anterior como parámetro. De esa forma puedes fx. pasar los resultados de la tarea principal a la tarea de continuación. – Thor

+1

@Thor: Gracias por traer eso a mi atención. Creo que lo tengo arreglado ahora. –

2

La solución de Brian no está completa y produce un error de sintaxis. Si no fuera por el error de sintaxis, funcionaría y resolvería el problema del póster inicial. No sé cómo corregir el error de sintaxis, así que estoy publicando esta pregunta para que se resuelva, de modo que la pregunta inicial se pueda resolver. Por favor, no borre este mensaje. es pertinente a la pregunta inicial que se responde.

@ Brian Gedeón: Su solución sería perfecto con la excepción del código siguiente:

// Define a continuation that happens after everything is done. 
parent.ContinueWith(
() => 
    { 
    // Code here will execute after the parent task has finished. 
    // You can safely update UI controls here. 
    }, TaskScheduler.FromCurrentSynchronizationContext); 

El problema específico con esto está en la parte() =>. Esto está produciendo un error de sintaxis que dice Delegado System.Action "System.Threading.Tasks.Task" no tiene argumentos 0

Realmente deseo que esto iba a funcionar y no sé la solución a esto. Intenté buscar el error, pero no entiendo qué parámetros requiere. Si alguien puede responder esto, sería extremadamente útil. Esta es la única pieza que falta a este problema.

+0

Gracias por traer esto a mi atención, he publicado una respuesta que explica el parámetro que requiere. – Thor

Cuestiones relacionadas