Mejor temporizador para usar en un servicio de Windows

108

Necesito crear un servicio de Windows que se ejecutará cada N período de tiempo.
La pregunta es:
¿Qué control de temporizador debo usar: System.Timers.Timero System.Threading.Timeruno? ¿Influye en algo?

Lo pregunto porque escuché muchas evidencias de un funcionamiento incorrecto System.Timers.Timeren los servicios de Windows.
Gracias.

shA.t
fuente

Respuestas:

118

Ambos System.Timers.Timery System.Threading.Timertrabajarán para servicios.

Los temporizadores que desea evitar son System.Web.UI.Timery System.Windows.Forms.Timer, que son respectivamente para aplicaciones ASP y WinForms. Usarlos hará que el servicio cargue un ensamblado adicional que realmente no es necesario para el tipo de aplicación que está creando.

Use System.Timers.Timercomo el siguiente ejemplo (también, asegúrese de usar una variable de nivel de clase para evitar la recolección de basura, como se indica en la respuesta de Tim Robinson):

using System;
using System.Timers;

public class Timer1
{
    private static System.Timers.Timer aTimer;

    public static void Main()
    {
        // Normally, the timer is declared at the class level,
        // so that it stays in scope as long as it is needed.
        // If the timer is declared in a long-running method,  
        // KeepAlive must be used to prevent the JIT compiler 
        // from allowing aggressive garbage collection to occur 
        // before the method ends. (See end of method.)
        //System.Timers.Timer aTimer;

        // Create a timer with a ten second interval.
        aTimer = new System.Timers.Timer(10000);

        // Hook up the Elapsed event for the timer.
        aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

        // Set the Interval to 2 seconds (2000 milliseconds).
        aTimer.Interval = 2000;
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program.");
        Console.ReadLine();

        // If the timer is declared in a long-running method, use
        // KeepAlive to prevent garbage collection from occurring
        // before the method ends.
        //GC.KeepAlive(aTimer);
    }

    // Specify what you want to happen when the Elapsed event is 
    // raised.
    private static void OnTimedEvent(object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}

/* This code example produces output similar to the following:

Press the Enter key to exit the program.
The Elapsed event was raised at 5/20/2007 8:42:27 PM
The Elapsed event was raised at 5/20/2007 8:42:29 PM
The Elapsed event was raised at 5/20/2007 8:42:31 PM
...
 */

Si lo desea System.Threading.Timer, puede utilizar lo siguiente:

using System;
using System.Threading;

class TimerExample
{
    static void Main()
    {
        AutoResetEvent autoEvent     = new AutoResetEvent(false);
        StatusChecker  statusChecker = new StatusChecker(10);

        // Create the delegate that invokes methods for the timer.
        TimerCallback timerDelegate = 
            new TimerCallback(statusChecker.CheckStatus);

        // Create a timer that signals the delegate to invoke 
        // CheckStatus after one second, and every 1/4 second 
        // thereafter.
        Console.WriteLine("{0} Creating timer.\n", 
            DateTime.Now.ToString("h:mm:ss.fff"));
        Timer stateTimer = 
                new Timer(timerDelegate, autoEvent, 1000, 250);

        // When autoEvent signals, change the period to every 
        // 1/2 second.
        autoEvent.WaitOne(5000, false);
        stateTimer.Change(0, 500);
        Console.WriteLine("\nChanging period.\n");

        // When autoEvent signals the second time, dispose of 
        // the timer.
        autoEvent.WaitOne(5000, false);
        stateTimer.Dispose();
        Console.WriteLine("\nDestroying timer.");
    }
}

class StatusChecker
{
    int invokeCount, maxCount;

    public StatusChecker(int count)
    {
        invokeCount  = 0;
        maxCount = count;
    }

    // This method is called by the timer delegate.
    public void CheckStatus(Object stateInfo)
    {
        AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
        Console.WriteLine("{0} Checking status {1,2}.", 
            DateTime.Now.ToString("h:mm:ss.fff"), 
            (++invokeCount).ToString());

        if(invokeCount == maxCount)
        {
            // Reset the counter and signal Main.
            invokeCount  = 0;
            autoEvent.Set();
        }
    }
}

Ambos ejemplos provienen de las páginas de MSDN.

Andrew Moore
fuente
1
¿Por qué sugieres: GC.KeepAlive (aTimer) ;, aTimer es la variable de instancia correcta, así que, por ejemplo, si es una variable de instancia de formulario, siempre habrá una referencia a ella siempre que haya formulario, no?
giorgim
37

No use un servicio para esto. Cree una aplicación normal y cree una tarea programada para ejecutarla.

Esta es la mejor práctica común. Jon Galloway está de acuerdo conmigo. O tal vez sea al revés. De cualquier manera, el hecho es que no es una buena práctica crear un servicio de Windows para realizar una tarea intermitente con un temporizador.

"Si está escribiendo un servicio de Windows que ejecuta un temporizador, debe volver a evaluar su solución".

–Jon Galloway, administrador del programa comunitario ASP.NET MVC, autor, superhéroe a tiempo parcial

Comunidad
fuente
26
Si su servicio está destinado a ejecutarse todo el día, entonces quizás tenga sentido un servicio en lugar de una tarea programada. O tener un servicio podría simplificar la administración y el registro para un grupo de infraestructura que no es tan inteligente como el equipo de aplicaciones. Sin embargo, cuestionar la suposición de que se requiere un servicio es completamente válido, y estas calificaciones negativas no se merecen. +1 a ambos.
sfuqua
2
@mr Tonterías. Las tareas programadas son una parte fundamental del sistema operativo. Guarde su FUD para usted.
4
@MR: No soy un evangelista, soy un realista. Y la realidad me ha enseñado que las tareas programadas no tienen "muchos errores". De hecho, depende de usted como persona que hizo esa afirmación para respaldarlo. De lo contrario, todo lo que está haciendo es esparcir miedo, incertidumbre y duda.
13
Odio meterme en el fango aquí, pero tengo que defender a MR un poco. Tengo varias aplicaciones críticas ejecutándose en mi empresa que son aplicaciones de consola de Windows. He usado el Programador de tareas de Windows para ejecutarlos todos. En al menos 5 ocasiones, hemos tenido un problema en el que el Servicio de Programador "se confundió" de alguna manera. Las tareas no se ejecutaron y algunas estaban en un estado extraño. La única solución fue reiniciar el servidor o detener e iniciar el servicio Programador. No es algo que pueda hacer sin derechos de administrador y no es aceptable en un entorno de producción. Solo mis $ 0.02.
SpaceCowboy74
6
De hecho, me ha pasado en algunos servidores (3 hasta ahora). Sin embargo, no diré que es la norma. Solo digo que a veces no está mal implementar tu propio método para hacer algo.
SpaceCowboy74
7

Cualquiera de los dos debería funcionar bien. De hecho, System.Threading.Timer usa System.Timers.Timer internamente.

Habiendo dicho eso, es fácil hacer un mal uso de System.Timers.Timer. Si no almacena el objeto Timer en una variable en algún lugar, es probable que se recolecte la basura. Si eso sucede, su temporizador ya no se disparará. Llame al método Dispose para detener el temporizador o utilice la clase System.Threading.Timer, que es un contenedor un poco más agradable.

¿Qué problemas has visto hasta ahora?

Tim Robinson
fuente
Me hace preguntarme por qué una aplicación de Windows Phone solo puede acceder a System.Threading.Timer.
Stonetip
Los teléfonos con Windows probablemente tengan una versión más ligera del marco, tener todo ese código adicional para poder utilizar ambos métodos probablemente no sea necesario, por lo que no está incluido. Creo que la respuesta de Nick da una mejor razón de por qué los teléfonos con Windows no tienen acceso System.Timers.Timerporque no manejarán las Excepciones que se le presentan.
Malaquías
2

Estoy de acuerdo con el comentario anterior que podría ser mejor considerar un enfoque diferente. Mi sugerencia sería escribir una aplicación de consola y usar el programador de Windows:

Esta voluntad:

  • Reducir el código de plomería que replica el comportamiento del programador
  • Proporcionar una mayor flexibilidad en términos de comportamiento de programación (por ejemplo, ejecutar solo los fines de semana) con toda la lógica de programación extraída del código de la aplicación
  • Utilice los argumentos de la línea de comando para los parámetros sin tener que configurar valores de configuración en archivos de configuración, etc.
  • Mucho más fácil de depurar / probar durante el desarrollo
  • Permita que un usuario de soporte ejecute invocando la aplicación de la consola directamente (por ejemplo, útil durante situaciones de soporte)
user32378
fuente
3
¿Pero esto requiere un usuario que haya iniciado sesión? Por lo tanto, el servicio es quizás mejor si se ejecuta 24 horas al día, 7 días a la semana en un servidor.
JP Hellemons
1

Como ya se ha dicho tanto System.Threading.Timery System.Timers.Timerva a funcionar. La gran diferencia entre los dos es que System.Threading.Timeres un envoltorio alrededor del otro.

System.Threading.Timertendrá más manejo de excepciones mientras que System.Timers.Timerse tragará todas las excepciones.

Esto me dio grandes problemas en el pasado, así que siempre usaba 'System.Threading.Timer' y aún manejaba muy bien sus excepciones.

Nick N.
fuente
0

Sé que este hilo es un poco antiguo, pero fue útil para un escenario específico que tenía y pensé que valía la pena señalar que hay otra razón por la que System.Threading.Timerpodría ser un buen enfoque. Cuando tiene que ejecutar periódicamente un trabajo que puede llevar mucho tiempo y desea asegurarse de que se usa todo el período de espera entre trabajos o si no desea que el trabajo se ejecute nuevamente antes de que el trabajo anterior haya terminado en el caso en que el trabajo tarda más que el período del temporizador. Puedes usar lo siguiente:

using System;
using System.ServiceProcess;
using System.Threading;

    public partial class TimerExampleService : ServiceBase
    {
        private AutoResetEvent AutoEventInstance { get; set; }
        private StatusChecker StatusCheckerInstance { get; set; }
        private Timer StateTimer { get; set; }
        public int TimerInterval { get; set; }

        public CaseIndexingService()
        {
            InitializeComponent();
            TimerInterval = 300000;
        }

        protected override void OnStart(string[] args)
        {
            AutoEventInstance = new AutoResetEvent(false);
            StatusCheckerInstance = new StatusChecker();

            // Create the delegate that invokes methods for the timer.
            TimerCallback timerDelegate =
                new TimerCallback(StatusCheckerInstance.CheckStatus);

            // Create a timer that signals the delegate to invoke 
            // 1.CheckStatus immediately, 
            // 2.Wait until the job is finished,
            // 3.then wait 5 minutes before executing again. 
            // 4.Repeat from point 2.
            Console.WriteLine("{0} Creating timer.\n",
                DateTime.Now.ToString("h:mm:ss.fff"));
            //Start Immediately but don't run again.
            StateTimer = new Timer(timerDelegate, AutoEventInstance, 0, Timeout.Infinite);
            while (StateTimer != null)
            {
                //Wait until the job is done
                AutoEventInstance.WaitOne();
                //Wait for 5 minutes before starting the job again.
                StateTimer.Change(TimerInterval, Timeout.Infinite);
            }
            //If the Job somehow takes longer than 5 minutes to complete then it wont matter because we will always wait another 5 minutes before running again.
        }

        protected override void OnStop()
        {
            StateTimer.Dispose();
        }
    }

    class StatusChecker
        {

            public StatusChecker()
            {
            }

            // This method is called by the timer delegate.
            public void CheckStatus(Object stateInfo)
            {
                AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
                Console.WriteLine("{0} Start Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                //This job takes time to run. For example purposes, I put a delay in here.
                int milliseconds = 5000;
                Thread.Sleep(milliseconds);
                //Job is now done running and the timer can now be reset to wait for the next interval
                Console.WriteLine("{0} Done Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                autoEvent.Set();
            }
        }
MigaelM
fuente