Кращий таймер для використання в сервісі Windows


108

Мені потрібно створити деяку службу Windows, яка виконуватиме N кожен проміжок часу.
Питання:
Який контроль таймера я повинен використовувати: System.Timers.Timerабо System.Threading.Timerодин? Чи впливає це на щось?

Я прошу, бо почув багато доказів неправильної роботи служб System.Timers.TimerWindows.
Дякую.

Відповіді:


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 Task Scheduler для запуску всіх. Щонайменше 5 випадків у нас виникла проблема, коли служба планувальників якось "заплуталася". Завдання не виконувались, і деякі були в дивному стані. Єдиним рішенням було перезавантаження сервера або зупинка та запуск служби Scheduler. Я не можу обійтися без прав адміністратора і не прийнятний у виробничому середовищі. Тільки мої 0,02 долара.
SpaceCowboy74

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

7

Або треба працювати нормально. Насправді System.Threading.Timer використовує System.Timers.Timer внутрішньо.

Сказавши це, легко використати System.Timers.Timer. Якщо ви десь не зберігаєте об'єкт «Таймер» у змінній, він може бути зібраний сміттям. Якщо це станеться, ваш таймер більше не запускатиметься. Викличте метод Dispose, щоб зупинити таймер, або скористайтеся класом System.Threading.Timer, який є трохи приємнішим обгортком.

Які проблеми ви бачили досі?


Змушує мене замислитися, чому програма Windows Phone може отримати доступ лише до System.Threading.Timer.
Стоунтіп

Напевно, телефони Windows мають легшу версію фреймворку, тому що весь цей додатковий код для використання обох методів, мабуть, не потрібен, тому він не входить. Я думаю, що відповідь Ніка дає кращу причину, чому телефони з Windows не мають доступу, System.Timers.Timerоскільки він не обробляє викинуті до нього виключення.
Малачі

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.