Служба Windows OnStop ждет завершения обработки

Я фактически разрабатываю службу Windows в VS 2012/.NET 4.5.

Служба работает по схеме фрагмента кода ниже:

  • Использование таймера
  • Выполняет требуемую операцию каждые пару минут.
  • Процесс занимает около 10 минут для завершения
  • Я использую один поток в сервисе

Я беспокоюсь о том, что если кто-то останавливает службу через консоль управления, это может быть только во время процесса, который выполняет служба.

Я кое-что прочитал об остановке службы Windows с остановкой запроса, но немного потерял. Иногда создаются WorkerThreads, иногда создаются ManualResetEvents, но до сих пор я не мог полностью понять лучший способ для своей службы Windows.

Мне нужно подождать, пока обработка не будет закончена в методе onStop до остановки службы Windows.

Каков наилучший способ продвижения, а также рассмотрение фрагмента кода ниже?

Спасибо всем!

namespace ImportationCV
{
    public partial class ImportationCV : ServiceBase
    {
        private System.Timers.Timer _oTimer;       

        public ImportationCV()
        {
            InitializeComponent();

            if (!EventLog.SourceExists(DAL.Utilities.Constants.LOG_JOURNAL))
            {
                EventLog.CreateEventSource(DAL.Utilities.Constants.LOG_JOURNAL,     DAL.Utilities.Constants.SOURCE_JOURNAL);
            }

            EventLog.Source = DAL.Utilities.Constants.SOURCE_JOURNAL;
            EventLog.Log = DAL.Utilities.Constants.LOG_JOURNAL;
        }

        protected override void OnStart(string[] args)
        {            
            int intDelai = Properties.Settings.Default.WatchDelay * 1000;

            _oTimer = new System.Timers.Timer(intDelai);
            _oTimer.Elapsed += new ElapsedEventHandler(this.Execute);

            _oTimer.Start();           

            EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " started at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
        }

        protected override void OnStop()
        {

            if (_oTimer != null && _oTimer.Enabled)
            {
                _oTimer.Stop();
                _oTimer.Dispose();
            }

            EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " stopped at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
        }

        private void Execute(object source, ElapsedEventArgs e)
        {
            _oTimer.Stop();

            try
            {                
                //Process


            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, (ex.StackTrace + ("\r\n" + ex.Message)), EventLogEntryType.Error);
            }

            _oTimer.Start();
        }
    }
}

Ответ 1

В качестве тестового примера я поместил вызов System.Threading.Thread.Sleep(500000) в обратный вызов OnStop() моей службы Windows. Я начал службу, а затем остановил ее. Я получил окно с индикатором выполнения, указывающим, что диспетчер управления службами (SCM) пытался остановить службу. Примерно через 2 минуты я получил ответ от SCM:

enter image description here

После того как я отклонил это окно, статус моей службы в SCM изменился на Stopping, и я заметил, что служба продолжала работать в диспетчере задач. После истечения сна (около 6 минут спустя) процесс прекратился. Обновление окна SCM показало, что служба больше не работает.

Я снимаю пару вещей. Во-первых, OnStop() должен действительно попытаться своевременно остановить службу, как часть игры с системой. Во-вторых, в зависимости от того, как структурирован ваш метод OnStop(), вы можете заставить службу игнорировать превентивный запрос на остановку, вместо этого останавливаясь, когда вы так говорите. Это не рекомендуется, но, похоже, вы можете это сделать.

Что касается вашей конкретной ситуации, то вам нужно понять, что событие System.Timers.Timer.Elapsed запускается в потоке ThreadPool. По определению это фоновый поток , что означает, что он не будет поддерживать приложение. Когда служба будет выключена, система остановит все фоновые потоки и выйдет из процесса. Поэтому ваша озабоченность по поводу продолжения обработки продолжается до тех пор, пока она не будет завершена, несмотря на то, что SCM сообщит об отключении не может, так как у вас есть структурированные в настоящее время вещи. Для этого вам нужно создать формальный объект System.Threading.Thread, установить его как поток переднего плана, а затем использовать таймер для запуска этого потока для выполнения (в отличие от выполнения в обратном вызове Elapsed).

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

Поэтому я бы предложил одну из двух вещей. Первый вариант - разбить обработку на отдельные куски, которые можно выполнить индивидуально. По завершении каждого фрагмента проверьте, остановлена ​​ли служба. Если это так, выйдите из потока изящно. Если этого не сделать, то я бы представил что-то вроде транзакций для вашей обработки. Скажем, что вам нужно взаимодействовать с кучей таблиц базы данных и прерывать поток после его начала, становится проблематичным, потому что база данных может быть оставлена ​​в плохом состоянии. Если система базы данных допускает транзакции, это становится относительно простым. Если нет, то сделайте всю обработку, которую вы можете в памяти, и зафиксируйте изменения в последнюю секунду. Таким образом, вы блокируете только блокировку, пока изменения выполняются, а не блокируются в течение всего времени. И для чего это стоит, я предпочитаю использовать ManualResetEvent для передачи команд shutdown в потоки.

Чтобы не путаться дальше, я отключу его здесь. НТН.

EDIT:

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

Определите два объекта ManualResetEvent, один для уведомления о завершении и один для обработки уведомления, и объект Thread. Измените обратный вызов OnStart() на это:

using System.Threading;
using Timer = System.Timers.Timer; // both Threading and Timers have a timer class

ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
ManualResetEvent _processEvent  = new ManualResetEvent(false);
Thread _thread;
Timer _oTimer;

protected override void OnStart(string[] args)
{
    // Create the formal, foreground thread.
    _thread = new Thread(Execute);
    _thread.IsBackground = false;  // set to foreground thread
    _thread.Start();

    // Start the timer.  Notice the lambda expression for setting the
    // process event when the timer elapses.
    int intDelai = Properties.Settings.Default.WatchDelay * 1000;
    _oTimer = new Timer(intDelai);
    _oTimer.AutoReset = false;
    _oTimer.Elapsed += (sender, e) => _processEvent.Set();
    _oTimer.Start();
}

Измените обратный вызов Execute() на что-то вроде этого:

private void Execute()
{
    var handles = new WaitHandle[] { _shutdownEvent, _processEvent };

    while (true)
    {
        switch (WaitHandle.WaitAny(handles))
        {
            case 0:  // Shutdown Event
                return; // end the thread
            case 1:  // Process Event
                Process();
                _processEvent.Reset();  // reset for next time
                _oTimer.Start();        // trigger timer again
                break;
        }
    }
}

Создайте метод Process() следующим образом:

private void Process()
{
    try
    {
        // Do your processing here.  If this takes a long time, you might
        // want to periodically check the shutdown event to see if you need
        // exit early.
    }
    catch (Exception ex)
    {
        // Do your logging here...

        // You *could * also shutdown the thread here, but this will not
        // stop the service.
        _shutdownEvent.Set();
    }
}

Наконец, в обратном вызове OnStop() запустите поток для завершения:

protected override void OnStop()
{
    _oTimer.Stop();  // no harm in calling it
    _oTimer.Dispose();

    _shutdownEvent.Set();  // trigger the thread to stop
    _thread.Join();        // wait for thread to stop
}

Ответ 2

@Matt - спасибо за отличный код, очень полезно. Я нашел, что это сработало еще лучше, если я добавил еще один тест на _shutdownEvent:

case 1:  // Process Event
            Process();
            if(_shutdownEvent.WaitOne(0)) break; // don't loop again if a shutdown is needed
...