it-swarm.com.ru

Служба 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();
        }
    }
}
13
crisjax

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

enter image description here

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

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

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

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

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

Чтобы не болтать дальше, я отрежу это здесь. НТН.

Правка:

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

Определите два объекта 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
}
19
Matt Davis

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

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