Melhor cronômetro para usar em um serviço do Windows

108

Preciso criar algum serviço do Windows que será executado a cada N períodos de tempo.
A questão é:
qual controle de cronômetro devo usar: System.Timers.Timerou System.Threading.Timerum? Isso influencia em alguma coisa?

Estou perguntando porque ouvi muitas evidências de não funcionamento correto dos System.Timers.Timerserviços do windows.
Obrigado.

shA.t
fonte

Respostas:

118

Ambos System.Timers.Timere System.Threading.Timerfuncionarão para serviços.

Os temporizadores que você deseja evitar são System.Web.UI.Timere System.Windows.Forms.Timer, que são respectivamente para aplicativos ASP e WinForms. O uso deles fará com que o serviço carregue um assembly adicional que não é realmente necessário para o tipo de aplicativo que você está construindo.

Use System.Timers.Timercomo o exemplo a seguir (também, certifique-se de usar uma variável de nível de classe para evitar a coleta de lixo, conforme declarado na resposta 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
...
 */

Se preferir System.Threading.Timer, você pode usar o seguinte:

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 os exemplos vêm das páginas do MSDN.

Andrew Moore
fonte
1
Por que você sugere: GC.KeepAlive (aTimer) ;, aTimer é a variável de instância certa, então por exemplo se for variável de instância de forma, sempre haverá referência a ela enquanto houver forma, não é?
giorgim
37

Não use um serviço para isso. Crie um aplicativo normal e crie uma tarefa agendada para executá-lo.

Esta é a melhor prática comumente aceita. Jon Galloway concorda comigo. Ou talvez seja o contrário. De qualquer forma, o fato é que não é uma prática recomendada criar um serviço do Windows para executar uma tarefa intermitente executada por um cronômetro.

"Se você está escrevendo um serviço do Windows que executa um cronômetro, deve reavaliar sua solução."

–Jon Galloway, gerente de programa da comunidade ASP.NET MVC, autor, super-herói de meio período

Comunidade
fonte
26
Se o seu serviço deve ser executado o dia todo, talvez um serviço faça sentido em vez de uma tarefa agendada. Ou, ter um serviço pode simplificar a administração e o registro para um grupo de infraestrutura que não seja tão experiente quanto a equipe de aplicativos. No entanto, questionar a suposição de que um serviço é necessário é totalmente válido e essas classificações negativas não são merecidas. +1 para ambos.
sfuqua
2
@mr Nonsense. Tarefas agendadas são uma parte central do sistema operacional. Por favor, mantenha seu FUD para você.
4
@MR: Não sou um evangelista, sou um realista. E a realidade me ensinou que as tarefas programadas não são "extremamente complicadas". Na verdade, depende de você, como pessoa que fez essa afirmação, apoiá-la. Caso contrário, tudo o que você está fazendo é espalhar medo, incerteza e dúvida.
13
Eu odeio entrar na lama aqui, mas eu tenho que defender MR um pouco. Tenho vários aplicativos críticos em execução na minha empresa que são aplicativos de console do Windows. Usei o Windows Task Scheduler para executar todos eles. Em pelo menos 5 ocasiões agora, tivemos um problema em que o serviço Scheduler "ficou confuso" de alguma forma. As tarefas não foram executadas e algumas estavam em um estado estranho. A única solução era reiniciar o servidor ou parar e iniciar o serviço Scheduler. Não é algo que posso fazer sem direitos de administrador e não é aceitável em um ambiente de produção. Apenas meus $ 0,02.
SpaceCowboy74
6
Na verdade, aconteceu comigo em alguns servidores (3 até agora). Não vou dizer que é a norma. Apenas dizendo que às vezes não é ruim implementar seu próprio método de fazer algo.
SpaceCowboy74
7

Qualquer um deve funcionar bem. Na verdade, System.Threading.Timer usa System.Timers.Timer internamente.

Dito isso, é fácil usar mal o System.Timers.Timer. Se você não armazenar o objeto Timer em uma variável em algum lugar, ele poderá ser coletado como lixo. Se isso acontecer, o cronômetro não disparará mais. Chame o método Dispose para interromper o cronômetro ou use a classe System.Threading.Timer, que é um wrapper ligeiramente melhor.

Que problemas você viu até agora?

Tim Robinson
fonte
Me faz pensar por que um aplicativo do Windows Phone só pode acessar System.Threading.Timer.
Stonetip
o windows phone provavelmente tem uma versão mais leve do framework, ter todo aquele código extra para poder usar os dois métodos provavelmente não é necessário, então ele não está incluído. Acho que a resposta de Nick dá uma razão melhor para explicar por que os telefones Windows não têm acesso, System.Timers.Timerporque ele não lida com as exceções lançadas nele.
Malachi
2

Concordo com o comentário anterior que pode ser melhor considerar uma abordagem diferente. Minha sugestão seria escrever um aplicativo de console e usar o agendador do Windows:

Isso vai:

  • Reduza o código de encanamento que replica o comportamento do planejador
  • Fornece maior flexibilidade em termos de comportamento de agendamento (por exemplo, executa apenas nos fins de semana) com toda a lógica de agendamento abstraída do código do aplicativo
  • Utilize os argumentos da linha de comando para parâmetros sem ter que definir valores de configuração em arquivos de configuração, etc.
  • Muito mais fácil de depurar / testar durante o desenvolvimento
  • Permitir que um usuário de suporte execute invocando o aplicativo de console diretamente (por exemplo, útil durante situações de suporte)
user32378
fonte
3
Mas isso requer um usuário conectado? Portanto, o serviço talvez seja melhor se funcionar 24 horas por dia, 7 dias por semana em um servidor.
JP Hellemons
1

Como já foi dito System.Threading.Timere System.Timers.Timerfuncionará. A grande diferença entre os dois é que System.Threading.Timerexiste um envoltório ao redor do outro.

System.Threading.Timerterá mais tratamento de exceção, enquanto System.Timers.Timerirá engolir todas as exceções.

Isso me deu grandes problemas no passado, então eu sempre usaria 'System.Threading.Timer' e ainda lidaria muito bem com suas exceções.

Nick N.
fonte
0

Sei que este tópico é um pouco antigo, mas foi útil para um cenário específico que eu tive e achei que vale a pena observar que há outro motivo pelo qual System.Threading.Timerpode ser uma boa abordagem. Quando você tem que executar periodicamente um trabalho que pode levar muito tempo e você deseja garantir que todo o período de espera seja usado entre os trabalhos ou se você não quiser que o trabalho seja executado novamente antes que o trabalho anterior termine no caso em que o trabalho leva mais tempo do que o período do cronômetro. Você pode usar o seguinte:

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
fonte