2009-11-19 15 views
14

Estoy manteniendo un código que se parece a esto. Es un servicio de Windows que hace algo de trabajo cada 30 minutos. El método ActualWorkDoneHere tarda unos 30 segundos en ejecutarse, pero si se detiene mientras se ejecuta, puede dejar cosas en mal estado. ¿Cuál es la mejor manera de evitar que eso suceda? ¿Debo reemplazar el While (verdadero) con un booleano que está configurado como falso en el método onstop (eliminando el hilo de la llamada Abort)? ¿Hay alguna forma de saber si un hilo está durmiendo?¿Cómo puedo detener con seguridad un hilo de C# .NET que se ejecuta en un servicio de Windows?

namespace WorkService 
{ 
    public partial class WorkService : ServiceBase 
    { 
     private Thread _workerThread = null; 

     public WorkService() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnStart(string[] args) 
     { 
      _workerThread = new Thread(new ThreadStart(DoWork)); 
      _workerThread.Start(); 
     } 

     protected override void OnStop() 
     { 
      _workerThread.Abort(); 
     } 

     static void DoWork() 
     { 
      int sleepMinutes = 30; 

      while (true) 
      { 
       ActualWorkDoneHere(); 

       System.Threading.Thread.Sleep(new TimeSpan(0, sleepMinutes, 0)); 
      } 
     } 
    } 
} 
+1

Esto parece un trabajo para el programador de tareas! :) –

+2

@Greg ¿El Programador de tareas no se ejecuta según un cronograma? En este caso Stimy está buscando pausar por 30 minutos y luego ejecutar nuevamente, no ejecutar cada 30 minutos independientemente. – Junto

Respuesta

22

Cuando tengo algo como esto, generalmente uso un ManualResetEvent. Esto se establece en la llamada Stop(). Luego, espero con un tiempo de espera:

for (;;) 
{ 
    if (_stop.WaitOne(timeout)) 
     break; 
    DoSomething(); 
} 
+1

+1 Además, si puede detener DoSomething() en unos pocos puntos sin dejar los datos en un estado incoherente, podría hacer un 'if (_stop.WaitOne (0)) {... return ...}' allí para evitar esperar hasta 30 s. – Gonzalo

+1

+1 Y uso un ManualResetEvent para disparar una señal cuando el hilo realmente se detiene. Entonces, el método de detención espera esta señal después de que le dice al hilo que se detenga. –

+0

No necesita un evento para decirle cuándo se detiene el hilo; solo llame a Thread.Join. –

2

Implementarlo usted mismo es la única opción segura. Incluso si encuentra una forma de averiguar si un hilo está durmiendo, aún tendrá una condición de carrera si intenta matarlo (porque posiblemente comience a procesarse después de comprobarlo y antes de matarlo).

En lugar de Thread.Sleep, puedes p. Ej. duerma 500ms y compruebe si el indicador de aborto sigue siendo falso, duerma otros 500ms, etc. antes de que pasen los 30 minutos, luego haga el trabajo, etc. (esto sería un enfoque pragmático). Si desea algo más elegante, puede usar un evento manualResetEvent con un tiempo de espera para esperar que el hilo principal indique que es hora de abortar.

0

Intente utilizar un indicador de autoreset para gestionar la detención del servicio. En ese caso, no tendrías que realizar el aborto de hilo. Han añadido el código de ejemplo siguiente

namespace WorkService 
{ 
    public partial class WorkService : ServiceBase 
    { 
    AutoResetEvent serviceStopEvent = new AutoResetEvent(false); 

     public WorkService() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnStart(string[] args) 
     { 
      Thread workerThread = new Thread(new ThreadStart(DoWork)); 
      workerThread.Start(); 
     } 

     protected override void OnStop() 
     { 
      serviceStopEvent.Set(); 
     } 

     static void DoWork() 
     { 
      int sleepMinutes = 30; 
     WaitHandle[ ] handles = new WaitHandle[ ] { serviceStopEvent }; 

      while (WaitHandle.WaitAny(handles)) 
      { 
       ActualWorkDoneHere(); 

      } 
     } 

    } 
} 

Cheers, Bharat.

+0

-1 El evento AutoResetEvent siempre se desactivará hasta detenerse y nunca se ejecutará ActualWorkDoneHere(). – Gonzalo

+0

Vaya. Olvidé agregar el evento de reinicio en el inicio. –

1

Aquí hay una manera de hacerlo. Añadir las siguientes variables a su clase:

private readonly object syncObject = new object(); 
private bool stopping; 
private bool stopped = true; 

Luego, en OnStart, se hace algo como esto (no tengo un método auxiliar que hace algo de tala en este ejemplo, y el método "Ejecutar" hace el trabajo real). :

public override void OnStart() 
    { 
     while (stopping) 
     { 
      Thread.Sleep(MSECS_SLEEP_FOR_STOP); 
     } 

     lock (syncObject) 
     { 
      // make sure task isn't already started 
      if (!stopped) 
      { 
       Helper.WriteToLog(logger, Level.INFO, 
        string.Format("{0} {1}", TASK_NAME, "is already started.")); 
       return; 
      } 
      stopped = false; 
     } 

     // start task in new thread 
     Thread thread = new Thread(Run); 
     thread.Start(); 

     Helper.WriteToLog(logger, Level.INFO, 
      string.Format("{0} {1}", TASK_NAME, "was started.")); 
    } 

Su "Ejecutar" método, que hace el trabajo de la rosca, se vería así (processInterval sería el tiempo que desea que esperar entre las corridas, se podría establecer en el constructor o simplemente codificar se):

private void Run() 
    { 
     try 
     { 
      while (!stopping) 
      { 
       // do work here 

       // wait for process interval 
       DateTime waitStart = DateTime.Now; 
       while (((DateTime.Now - waitStart).TotalMilliseconds < processInterval) && !stopping) 
       { 
        // give processing time to other threads 
        Thread.Sleep(MSECS_SLEEP_FOR_CHECK); 
       } 
      } 
      lock (syncObject) 
      { 
       stopped = true; 
       stopping = false; 
      } 

      Helper.WriteToLog(logger, Level.INFO, 
       string.Format("{0} {1}", TASK_NAME, "was stopped.")); 
     } 
     catch (Exception e) 
     { 
      // log the exception, but ignore it (i.e. don't throw it) 
      Helper.LogException(logger, MethodBase.GetCurrentMethod(), e); 
     } 
    } 

Luego, en OnStop, que podría hacer esto:

public override void OnStop() 
    { 
     lock (syncObject) 
     { 
      if (stopping || stopped) 
      { 
       Helper.WriteToLog(logger, Level.INFO, 
        string.Format("{0} {1}", TASK_NAME, "is already stopped.")); 
       return; 
      } 
      stopping = true; 
     } 
    } 
1

Se puede usar un objeto de bloqueo para evitar que el hilo se detuvo, mientras que su trabajo está sucediendo realmente ...

private static readonly object _syncRoot = new object(); 

    protected override void OnStop() 
    { 
     lock (_syncRoot) 
     { 
      _workerThread.Abort(); 
     } 
    } 

    static void DoWork() 
    { 
     int sleepMinutes = 30; 

     while (true) 
     { 
      lock (_syncRoot) 
      { 
       ActualWorkDoneHere(); 
      } 

      System.Threading.Thread.Sleep(new TimeSpan(0, sleepMinutes, 0)); 
     } 
    } 

usted debe tener cuidado sin embargo, si su función ActualWorkDoneHere() lleva demasiado tiempo, Windows informará que el servicio no puede detenerse.

2

Wow, todo el mundo lo hace tan complicado. Use un Temporizador:

En las carreras: La publicación original tenía una carrera en OnStop que se ha corregido. Hasta donde sé, poner el servicio en un estado detenido no abortará los subprocesos de subprocesos que se utilizan para dar servicio al temporizador. La condición de la activación del temporizador y la detención del servicio al mismo tiempo son irrelevantes.ActualWorkDoneHere() se ejecutará o no se ejecutará. Ambas son condiciones aceptables.

namespace WorkService 
{ 
    public partial class WorkService : ServiceBase 
    { 
     protected const int sleepMinutes = 30; 
     protected System.Timers.Timer _interval; 
     protected bool _running = false; 

     public WorkService() 
     { 
      InitializeComponent(); 
      _interval = new System.Timers.Timer(); 
      _interval.Elapsed += new ElapsedEventHandler(OnTimedEvent); 
      _interval.Interval = sleepMinutes * 60 * 1000; 
      _running = false; 
     } 

     protected override void OnStart(string[] args) 
     { 
      _running = true; 
      _interval.Enabled = true; 
     } 

     protected override void OnStop() 
     { 
      _interval.Enabled = false; 
      _running = false; 
     } 

     private static void OnTimedEvent(object source, ElapsedEventArgs e) 
     { 
      if(_running) 
       ActualWorkDoneHere(); 
     } 
    } 
} 
+0

existen condiciones de carrera. – scottm

+0

si alguien todavía siente que hay una condición de carrera por favor explique. – JeffreyABecker

+0

@JeffreyABecker de forma predeterminada, el temporizador puede ejecutar más de un hilo simultáneamente, por lo tanto, existe la posibilidad de condiciones de carrera. El evento ManualResetEvent siempre se ejecutará de forma predeterminada en un hilo. –

0

Mi servicio escucha en una toma de red de modo que lo que hice es crear un par unido de conectores de red y se utiliza la llamada al sistema select para escuchar en ambos. Si el par unido informó que estaba listo para leer, supe cerrar el servicio.

Este truco se puede usar para disparar un número arbitrario de hilos, siempre y cuando ninguno de ellos realmente lea del par conectado.

0
while (true) 
     { 
      if (m_reset.WaitOne(1,false)) 
       break; 
      // DoSomething 


     } 

favor Prueba esto dentro onStop()

Cuestiones relacionadas