2010-09-13 16 views
7

Planteamiento del problemala forma de terminar un subproceso de trabajo correctamente en C#

Tengo un subproceso de trabajo que básicamente explora una carpeta, de entrar en los archivos dentro de ella, y luego se duerme durante un tiempo. La operación de escaneo puede tomar de 2 a 3 segundos, pero no mucho más. Estoy buscando una manera de detener este hilo elegantemente.

Aclaración: Quiero detener el hilo, mientras que es dormir, y no mientras que es de exploración. Sin embargo, el problema es que no sé cuál es el estado actual del hilo. Si está durmiendo, quiero que salga inmediatamente. Si está escaneando, quiero que salga en el momento en que intenta bloquearlo.

Los intentos de una solución

Al principio estaba usando sueño y de alarma. Luego descubrí que interrumpir realmente no interrumpe el sueño, solo funciona cuando los hilos intentan entrar en modo de suspensión.

Así que cambié a Monitor Wait & Pulse. Luego descubrí que el Pulso solo funciona cuando estoy realmente en Espera. Así que ahora tengo un hilo que se ve así:

while (m_shouldRun) 
{ 
    try 
    { 
     DoSomethingThatTakesSeveralSeconds(); 
     lock (this) 
     { 
      Monitor.Wait(this, 5000); 
     } 
    } 
    catch (ThreadInterruptedException) 
    { 
     m_shouldRun = false; 
    } 
} 

Y ahora tengo que crear mi función Detener. Así que empecé con:

public void Stop() 
{ 
    m_shouldRun = false; 
    lock (this) 
    { 
     Monitor.Pulse(this); 
    } 
    thread.Join(); 
} 

Pero esto no funciona porque se me puede pulsante mientras que las obras de hilo (si bien no está esperando). Por lo que añade interrupción:

public void Stop() 
{ 
    m_shouldRun = false; 
    thread.Interrupt(); 
    lock (this) 
    { 
     Monitor.Pulse(this); 
    } 
    thread.Join(); 
} 

Otra opción es utilizar:

public void Stop() 
{ 
    m_shouldRun = false; 
    while (!thread.Join(1000)) 
    { 
     lock (this) 
     { 
      Monitor.Pulse(this); 
     } 
    } 
} 

La pregunta

¿Cuál es el método preferido? ¿Hay un tercer método que sea preferible?

+0

"* Al principio yo estaba usando sueño y de interrupción Luego descubrí que Interrupt realmente no interrumpe el Sueño, solo funciona cuando los hilos INTENTAN entrar a dormir. * "Eso es extraño. ¿Es esto un error conocido? Si no es así, ¿puedes tratar de reproducirlo con un ejemplo de juguete? – Heinzi

Respuesta

7

Otra alternativa es utilizar eventos:

private ManualResetEvent _event = new ManualResetEvent(false); 


public void Run() 
{ 
while (true) 
{ 
    DoSomethingThatTakesSeveralSeconds(); 
    if (_event.WaitOne(timeout)) 
     break; 
} 
} 

public void Stop() 
{ 
    _event.Set(); 
    thread.Join(); 
} 
+0

Sí, esto funcionará también. La pregunta es cuál sería la mejor manera de hacerlo. Su opción se ve mejor que el pulso recurrente o la interrupción + pulso. –

+0

+1, sí, esto es * mucho * mejor. –

+1

Bueno, personalmente no usaría Pulse/Interrupts. El pulso puede ser problemático debido a posibles APC (creo que podría pasar desapercibido por el hilo). Las interrupciones no tienen una buena reputación: http://www.bluebytesoftware.com/blog/2007/08/23/ThreadInterruptsAreAlmostAsEvilAsThreadAborts.aspx – liggett78

9

La manera de detener un hilo elegantemente es dejarlo terminar por sí mismo. Entonces, dentro del método de trabajador podría tener una variable booleana que verificará si queremos interrumpir. De manera predeterminada, se establecerá en false y cuando lo configure en true desde el hilo principal, simplemente detendrá la operación de escaneo rompiendo el ciclo de procesamiento.

+3

+1 para permitir que el hilo termine solo. Cualquier otro enfoque es desordenado. No te olvides de marcar el indicador booleano con la palabra clave volátil. – spender

+1

Gracias. Notarás que sí tengo esa bandera. No interrumpo el hilo mientras está funcionando, pero sí quiero interrumpirlo mientras duerme. Si va a dormir 10 minutos, no quiero que continúe durmiendo. –

+0

Un hilo que duerme durante 10 segundos no es útil para nadie. Use 'ThreadPool' para dibujar hilos cada vez que necesite realizar algunas tareas, pero no las deje durmiendo. Hazlos hacer cosas útiles. –

1

recomiendo que sea sencillo:

while (m_shouldRun) 
{ 
    DoSomethingThatTakesSeveralSeconds(); 
    for (int i = 0; i < 5; i++) // example: 5 seconds sleep 
    { 
     if (!m_shouldRun) 
      break; 
     Thread.Sleep(1000); 
    } 
} 

public void Stop() 
{ 
    m_shouldRun = false; 
    // maybe thread.Join(); 
} 

Esto tiene las siguientes ventajas:

  • huele a la espera activa, pero no lo es. Se realizan $ NUMBER_OF_SECONDS cheques durante la fase de espera, que no es comparable a los miles de cheques realizados en espera real.
  • Es simple, lo que reduce en gran medida el riesgo de error en el código de subprocesos múltiples. Todo lo que su Stop método necesita hacer es establecer m_shouldRun en falso y (tal vez) llamar a Thread.Join (si es necesario que el subproceso finalice antes de que Stop quede).No se necesitan primitivas de sincronización (excepto para marcar m_shouldRun como volátil).
+0

La función DoSomething no se interrumpirá con fuerza. Thread.Interrupt solo "sucede" cuando el hilo intenta bloquear. Consulte la documentación de MS (aquí: http://msdn.microsoft.com/en-us/library/system.threading.thread.interrupt.aspx) - "Si este hilo no está actualmente bloqueado en una espera, sueño o unión estado, se interrumpirá cuando el próximo comience a bloquearse ". –

+0

@ Eldad: Buen punto, lo confundí con Thread.Abort. Cambió mi respuesta. – Heinzi

0

me ocurrió programar por separado la tarea:.

using System; 
using System.Threading; 

namespace ProjectEuler 
{ 
    class Program 
    { 
     //const double cycleIntervalMilliseconds = 10 * 60 * 1000; 
     const double cycleIntervalMilliseconds = 5 * 1000; 
     static readonly System.Timers.Timer scanTimer = 
      new System.Timers.Timer(cycleIntervalMilliseconds); 
     static bool scanningEnabled = true; 
     static readonly ManualResetEvent scanFinished = 
      new ManualResetEvent(true); 

     static void Main(string[] args) 
     { 
      scanTimer.Elapsed += 
       new System.Timers.ElapsedEventHandler(scanTimer_Elapsed); 
      scanTimer.Enabled = true; 

      Console.ReadLine(); 
      scanningEnabled = false; 
      scanFinished.WaitOne(); 
     } 

     static void scanTimer_Elapsed(object sender, 
      System.Timers.ElapsedEventArgs e) 
     { 
      scanFinished.Reset(); 
      scanTimer.Enabled = false; 

      if (scanningEnabled) 
      { 
       try 
       { 
        Console.WriteLine("Processing"); 
        Thread.Sleep(5000); 
        Console.WriteLine("Finished"); 
       } 
       finally 
       { 
        scanTimer.Enabled = scanningEnabled; 
        scanFinished.Set(); 
       } 
      } 
     } 
    } 
} 
Cuestiones relacionadas