Windows Service System.Timers.Timer не работает

У меня есть служба Windows, написанная на С#, которая предназначена для выполнения задачи каждые несколько минут. Я использую System.Timers.Timer для этого, но он никогда не срабатывает. Я смотрел много разных сообщений здесь, в SO и в других местах, и я не вижу, что не так с моим кодом.

Вот мой код с удаленными таймерами для ясности...

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Timers.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
            IntervalTimer = new System.Timers.Timer(60000);  // Default in case app.config is silent.
            IntervalTimer.Enabled = false;
            IntervalTimer.Elapsed += new ElapsedEventHandler(this.IntervalTimer_Elapsed);
        }

        protected override void OnStart(string[] args)
        {
            // Set up the timer...
            IntervalTimer.Enabled = false;
            IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
            // Start the timer and wait for the next work to be released...
            IntervalTimer.Start();
        }

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

        private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {   // Do the thing that needs doing every few minutes...
            DoWork();
        }
    }
}

Я действительно почесываю голову над этим. Может ли кто-нибудь определить, что глупо, что я ошибаюсь?

EDIT: По предложению я добавил IntervalTimer.Enabled = true; до IntervalTimer.Start(); в методе OnStart службы. Это не решит проблему.

Я добавил файл трассировки в службу, чтобы подтвердить некоторые из внутренних элементов, и я точно знаю, что значение Timer.Enabled истинно к завершению завершения OnStart().

Ответ 1

Вот моя работа...

Спустя слишком много часов, чтобы найти ответ на этот вопрос, я обнаружил множество статей и блогов о таймерах в службах Windows. Я видел много мнений по этому поводу, и все они делятся на три категории и в порядке убывания частоты:

  • Не используйте System.Windows.Forms.Timer, потому что это не сработает. (это имеет смысл)

  • Не используйте System.Threading.Timer, потому что он не работает, вместо этого используйте System.Timers.Timer.

  • Не используйте System.Timers.Timer, потому что он не работает, вместо этого используйте System.Threading.Timer.

Основываясь на этом, я попробовал 2. Это также подход, который, по-видимому, рекомендован Microsoft, поскольку они говорят, что System.Timers.Timer подходит для "Серверных приложений" .

Я обнаружил, что System.Timers.Timer просто не работает в моем приложении Windows Service. Поэтому я переключился на System.Threading.Timer. Это неприятность, поскольку для ее работы требуется некоторое рефакторинг.

Это примерно то, что теперь выглядит мой рабочий код...

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Threading.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            TimeSpan tsInterval = new TimeSpan(0, 0, Properties.Settings.Default.PollingFreqInSec);
            IntervalTimer = new System.Threading.Timer(
                new System.Threading.TimerCallback(IntervalTimer_Elapsed)
                , null, tsInterval, tsInterval);
        }

        protected override void OnStop()
        {
            IntervalTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            IntervalTimer.Dispose();
            IntervalTimer = null;
        }

        private void IntervalTimer_Elapsed(object state)
        {   // Do the thing that needs doing every few minutes...
            // (Omitted for simplicity is sentinel logic to prevent re-entering
            //  DoWork() if the previous "tick" has for some reason not completed.)
            DoWork();
        }
    }
}

Я ненавижу "Доктор, доктор, больно, когда я делаю это...", но это то, к чему мне приходилось прибегать. Еще одно мнение о куче для следующего парня с этой проблемой...

Ответ 2

Вы забыли включить таймер, установив:

IntervalTimer.Enabled = true;

или вызов метода Start:

IntervalTimer.Start();
protected override void OnStart(string[] args)
{
    // Set up the timer...
    IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
    // Start the timer and wait for the next work to be released...
    IntervalTimer.Start();
}

Ответ 3

По-видимому, System.Timers.Timer скрывает любые исключения, проглатывает их тихо, а затем задыхается. Разумеется, вы можете обработать их в своем методе, который вы добавили в качестве обработчика к вашему таймеру, но если исключение сразу же вызывается на входе (до того, как выполняется первая строка кода, что может произойти, если ваш метод объявляет переменную который использует объект в сильноименованной DLL, из которого вы, например, имеете неправильную версию), вы никогда не увидите это исключение.

И ты собираешься присоединиться к нам всем, чтобы вырвать волосы.

Или вы можете это сделать:

  • создать метод оболочки, который (в цикле try-catch) вызывает метод, который вы хотели бы выполнить. Если этот метод умирает от вас, обернутый метод может выполнять обработку исключений, не убивая таймер, потому что, если вы не остановите таймер, он никогда не заметит, что что-то пошло не так.

(Я остановил таймер, потому что, если он терпит неудачу, попытка снова не имеет смысла для этого конкретного приложения...)

Надеюсь, это поможет тем, кто приземлился здесь из Google (как и я).

Ответ 4

Чтобы добавить к тому, что написал user1820848, потому что это была моя проблема, если ваше прошедшее событие System.timers.timer не похоже на запуск, поместите все в обработчик события в блок try/catch, и ищите любую проблему там. Я пробовал все рекомендуемые методы для решения этой проблемы (или думал, что у меня есть), включая переход с system.timers.timer на system.threading.timer, и это тоже не сработало.

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

У меня была ситуация, когда на этом сервере у меня три запущенных сервисов, выполняющих по существу один и тот же код таймера. Я даже пошел по очереди с другим запущенным служебным кодом, чтобы убедиться, что я делаю system.timers.timer, обрабатывая то же самое. Но другой сервис работает отлично, и этот, похоже, вообще не уволил событие.

Проблема, как оказалось, заключалась в том, что в моих начальных операторах dim я запускал класс, который пытался подключиться к Oracle. Этот вызов терпел неудачу, но на самом деле он не срабатывал, потому что версия клиента Oracle на моей рабочей станции и сервере несколько отличалась. Это произошло, когда среда CLR разрешала ссылки, поэтому она не была поймана в моих базовых классах try/catch. Если бы я отлаживал, отладчик отметил бы ошибку. Запустив сервер, CLR не смогла рассказать мне о проблеме. Таким образом, моя служба просто сидела там с неповрежденной ошибкой.

Включение всего в try/catch сразу указывало на проблему. Поместите свою попытку перед любыми объявлениями в этой подпрограмме. Если вы ошибаетесь в очень раннем заявлении, как вы его поймаете.

[Извините за отдельный ответ, но вы должны предоставить ответы, чтобы получить достаточную репутацию, чтобы даже прокомментировать кто-то другой ответ?!?]

[Edit: еще одна вещь, которую нужно попробовать: вывести свой код из события таймера, поместить его в другую суб/функцию, вызвать это из кода запуска, а также поместить вызов функции в событие таймера. Несколько недель спустя, на моей рабочей станции, я пытаюсь запустить тот же код, и у меня есть ощущение, что мое событие таймера не вызвано, и я был здесь раньше. В самом деле! Но положить все в попытку/улов тоже не работает!?! Переместил его на вызов функции и Bam, там мое исключение - Oracle снова. Но он не появлялся даже с каждой отдельной строкой внутри try/catch, пока я не переместил код из события таймера и не попытался снова.]

Ответ 5

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

/// <summary>
/// Custom Timer class, that is actually a wrapper over System.Threading.Timer
/// </summary>
/// <seealso cref="System.IDisposable" />
internal class Timer : IDisposable
{
    System.Threading.Timer _timer;

    public Timer()
    {

    }
    public Timer(int interval) : this()
    {
        this.Interval = interval;
    }

    public bool AutoReset { get; set; }
    public bool Enabled { get; set; }
    public int Interval { get; set; }
    public Action<object> OnTimer { get; internal set; }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            _timer.Dispose();
            _timer = null;
        }
    }

    public void Start()
    {
        _timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(OnTimer), null, 0, Interval);
    }
    public void Stop()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
        }
    }
}

Надеюсь, это поможет!

Ответ 6

 private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {   // Do the thing that needs doing every few minutes...
        DoWork();

        //Add following 2 lines. It will work.
        **IntervalTimer.Interval= 100; //any value
        IntervalTimer.Start();**
    }