Лучший таймер для использования в службе Windows


108

Мне нужно создать службу Windows, которая будет выполняться каждые N периодов времени.
Возникает вопрос:
какой таймер мне следует использовать: System.Timers.Timerили System.Threading.Timerодин? На что-то влияет?

Я спрашиваю, потому что слышал много свидетельств некорректной работы System.Timers.Timerслужб Windows.
Спасибо.

Ответы:


118

Оба так System.Timers.Timerи System.Threading.Timerбудут работать на сервисы.

Таймеры, которых следует избегать, - это System.Web.UI.Timerи System.Windows.Forms.Timer, которые предназначены соответственно для приложений ASP и WinForms. Их использование приведет к загрузке службой дополнительной сборки, которая на самом деле не нужна для того типа приложения, которое вы создаете.

Используйте System.Timers.Timerследующий пример (также убедитесь, что вы используете переменную уровня класса, чтобы предотвратить сборку мусора, как указано в ответе Тима Робинсона):

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
...
 */

Если вы выберете System.Threading.Timer, вы можете использовать следующее:

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();
        }
    }
}

Оба примера взяты со страниц MSDN.


1
Почему вы предлагаете: GC.KeepAlive (aTimer) ;, aTimer - это переменная экземпляра справа, поэтому, например, если это переменная экземпляра формы, всегда будет ссылка на нее, пока есть форма, не так ли?
giorgim

37

Не используйте для этого сервис. Создайте обычное приложение и создайте запланированную задачу для его запуска.

Это общепринятая передовая практика. Джон Гэллоуэй согласен со мной. Или, может быть, все наоборот. В любом случае, дело в том, что создание службы Windows для выполнения периодической задачи, запускаемой по таймеру, не рекомендуется.

«Если вы пишете службу Windows, которая запускает таймер, вам следует пересмотреть свое решение».

–Джон Галлоуэй, менеджер программы сообщества ASP.NET MVC, автор, супергерой по совместительству


26
Если ваша служба предназначена для работы весь день, то, возможно, она имеет смысл, а не запланированное задание. Или наличие службы может упростить администрирование и ведение журнала для группы инфраструктуры, которая не так сообразительна, как группа разработчиков. Однако ставить под сомнение предположение о том, что услуга требуется, полностью обосновано, и эти отрицательные оценки незаслуженно. +1 к обоим.
sfuqua

2
@mr Чепуха. Запланированные задачи - это основная часть ОС. Пожалуйста, держите свой FUD при себе.

4
@MR: Я не евангелист, я реалист. И реальность научила меня, что запланированные задачи не «сильно глючат». Фактически, это зависит от вас, как человека, который сделал это заявление, чтобы поддержать его. В противном случае вы сеете страх, неуверенность и сомнения.

13
Ненавижу забираться здесь в трясину, но мне нужно немного защищать MR. В моей компании работает несколько важных приложений, которые являются консольными приложениями Windows. Я использовал планировщик задач Windows для запуска всех из них. По крайней мере, 5 раз мы сталкивались с проблемой, при которой служба планировщика каким-то образом "запутывалась". Задачи не выполнялись, некоторые находились в странном состоянии. Единственным решением была перезагрузка сервера или остановка и запуск службы планировщика. Я не могу обойтись без прав администратора и неприемлемо в производственной среде. Только мои 0,02 доллара.
SpaceCowboy74

6
На самом деле это случилось со мной на нескольких серверах (пока 3). Не скажу, что это норма. Просто скажу, что иногда неплохо реализовать свой собственный метод действий.
SpaceCowboy74

7

Либо одно должно работать нормально. Фактически, System.Threading.Timer внутренне использует System.Timers.Timer.

Сказав это, легко злоупотребить System.Timers.Timer. Если вы не храните объект Timer где-нибудь в переменной, он может быть собран сборщиком мусора. Если это произойдет, ваш таймер больше не сработает. Вызовите метод Dispose, чтобы остановить таймер, или используйте класс System.Threading.Timer, который представляет собой более удобную оболочку.

Какие проблемы вы уже видели?


Это заставляет меня задуматься, почему приложение Windows Phone может получить доступ только к System.Threading.Timer.
Stonetip

телефоны с Windows, вероятно, имеют более легкую версию фреймворка, и весь этот дополнительный код для использования обоих методов, вероятно, не нужен, поэтому он не включен. Я думаю, что ответ Ника дает лучшую причину того, почему телефоны Windows не имеют доступа, System.Timers.Timerпотому что он не обрабатывает брошенные ему исключения.
Malachi

2

Я согласен с предыдущим комментарием, что, возможно, лучше всего рассмотреть другой подход. Я предлагаю написать консольное приложение и использовать планировщик Windows:

Это будет:

  • Уменьшите количество кода, воспроизводящего поведение планировщика
  • Обеспечение большей гибкости с точки зрения поведения планирования (например, запуск только по выходным), при этом вся логика планирования абстрагируется от кода приложения.
  • Используйте аргументы командной строки для параметров без необходимости устанавливать значения конфигурации в файлах конфигурации и т. Д.
  • Намного проще отлаживать / тестировать во время разработки
  • Разрешить пользователю службы поддержки выполнять действия путем прямого вызова консольного приложения (например, полезно в ситуациях поддержки)

3
Но для этого требуется авторизованный пользователь? Так что сервис может быть лучше, если он будет работать на сервере 24/7.
JP Hellemons

1

Как уже было сказано, оба так System.Threading.Timerи System.Timers.Timerбудут работать. Большая разница между ними в том, что System.Threading.Timerвокруг другого находится оболочка.

System.Threading.Timerбудет больше обработки исключений, в то время как System.Timers.Timerпроглотит все исключения.

В прошлом это доставляло мне большие проблемы, поэтому я всегда использовал System.Threading.Timer и все еще очень хорошо обрабатывал ваши исключения.


0

Я знаю, что эта ветка немного устарела, но она пригодилась для конкретного сценария, который у меня был, и я подумал, что стоит отметить, что есть еще одна причина, по которой System.Threading.Timerможет быть хороший подход. Когда вам нужно периодически выполнять задание, которое может занять много времени, и вы хотите, чтобы между заданиями использовался весь период ожидания, или если вы не хотите, чтобы задание запускалось снова до завершения предыдущего задания в случае, когда задание занимает больше времени, чем период таймера. Вы можете использовать следующее:

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();
            }
        }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.