Как правильно остановить многопоточную службу Windows .NET? - PullRequest
24 голосов
/ 07 октября 2009

У меня есть служба Windows, написанная на C #, которая создает загрузку потоков и выполняет множество сетевых подключений (WMI, SNMP, простой TCP, http). При попытке остановить службу Windows с помощью оснастки «Службы MSC» вызов остановки службы возвращается относительно быстро, но процесс продолжает выполняться в течение примерно 30 секунд.

Основной вопрос заключается в том, что может быть причиной того, что для остановки требуется более 30 секунд. Что я могу искать и как мне искать его?

Вторичный вопрос - почему сервисная оснастка msc (сервисный контроллер) возвращается, даже если процесс все еще работает. Есть ли способ заставить его вернуться только тогда, когда процесс действительно убит?

Вот код в методе OnStop службы

protected override void OnStop()
{
   //doing some tracing
   //......

   //doing some minor single threaded cleanup here
   //......

   base.OnStop();

   //doing some tracing here
}

Редактировать в ответ на ответы очистки темы

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

Интересно проверить флаг IsBackround. Опять же, как я могу узнать, есть ли у меня какие-либо форпредовые потоки, работающие с arround? Мне придется проверять каждый раздел кода, который создает поток. Есть ли другой способ, может быть, инструмент, который может помочь мне выяснить это.

В конечном счете, процесс действительно останавливается. Казалось бы только, что мне нужно чего-то ждать. Однако, если я жду в методе OnStop X времени, то для остановки процесса потребуется примерно 30 секунд + X. Независимо от того, что я пытаюсь сделать, кажется, что процессу требуется приблизительно 30 секунд (его не всегда 30 секунд, он может варьироваться) после того, как OnStop вернется, чтобы процесс фактически остановился.

Ответы [ 7 ]

18 голосов
/ 07 октября 2009

Звонок для остановки службы возвращается, как только ваш OnStop() обратный вызов возвращается. Исходя из того, что вы показали, ваш OnStop() метод мало что дает, что объясняет, почему он возвращается так быстро.

Есть несколько способов заставить ваш сервис завершиться.

Сначала вы можете переработать метод OnStop(), чтобы подать сигнал на закрытие всех потоков и дождаться их закрытия перед выходом. Как подсказывает @DSO, вы можете использовать глобальный флаг bool для этого (обязательно отметьте его как volatile). Я обычно использую ManualResetEvent, но любой из них будет работать. Сигнализировать потоки для выхода. Затем присоединитесь к потокам с некоторым периодом ожидания (я обычно использую 3000 миллисекунд). Если к тому времени потоки еще не вышли, вы можете вызвать метод Abort() для их выхода. Обычно метод Abort() не одобряется, но, учитывая, что ваш процесс в любом случае завершается, это не имеет большого значения. Если у вас постоянно есть поток, который необходимо прервать, вы можете переработать этот поток, чтобы он был более чувствительным к вашему сигналу отключения.

Во-вторых, пометьте ваши темы как background темы (подробнее см. здесь ). Похоже, вы используете класс System.Threading.Thread для потоков, которые по умолчанию являются приоритетными. Это позволит убедиться, что потоки не удерживают процесс от выхода. Это будет хорошо работать, если вы выполняете только управляемый код. Если у вас есть поток, ожидающий неуправляемого кода, я не уверен, что установка свойства IsBackground по-прежнему приведет к автоматическому завершению потока при завершении работы, т. Е. Вам, возможно, придется переделывать модель потоков, чтобы этот поток реагировал на ваш запрос. запрос на отключение.

11 голосов
/ 07 октября 2009

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

Общий подход состоит в том, чтобы OnStop сигнализировал всем вашим потокам об остановке, а затем ожидал их остановки. Чтобы избежать бесконечной блокировки, вы можете дать нитям ограничение по времени для остановки, а затем прервать их, если они займут слишком много времени.

Вот что я делал в прошлом:

  1. Создать глобальный флаг bool под названием Стоп, установите в false, когда служба запущен.
  2. Когда вызывается метод OnStop, установите для флага Stop значение true, а затем выполните Thread.Join для всех ожидающих рабочих потоков.
  3. Каждый рабочий поток отвечает за проверку флага Stop и корректно завершается, когда он равен true. Эту проверку следует выполнять часто и всегда перед длительной работой, чтобы избежать слишком долгой задержки отключения службы.
  4. В методе OnStop также есть тайм-аут для вызовов Join, чтобы дать потокам ограниченное время для чистого выхода ... после чего вы просто прерываете его.

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

1 голос
/ 07 октября 2009

Простой способ сделать это может выглядеть так:
-первый крит глобальное событие

ManualResetEvent shutdownEvent;

-при запуске службы создайте событие ручного сброса и установите его в исходное состояние без сигнала
shutdownEvent = new ManualResetEvent(false);

- при остановке сервиса

shutdownEvent.Set();

не забудьте дождаться окончания темы
do
{
 //send message for Service Manager to get more time
 //control how long you wait for threads stop
}
while ( not_all_threads_stopped );

- каждый поток должен время от времени проверять событие на остановку

if ( shutdownEvent.WaitOne(delay, true) ) break;
0 голосов
/ 29 сентября 2015

Мэтт Дэвис довольно завершен.
Несколько баллов; Если у вас есть поток, который работает вечно (потому что он имеет почти бесконечный цикл и перехватывает все), и задача вашего сервиса - запустить этот поток, вы, вероятно, захотите, чтобы он был приоритетным потоком.

Кроме того, если какая-либо из ваших задач выполняет более длительную операцию, такую ​​как вызов sproc, и поэтому ваш тайм-аут соединения должен быть немного длиннее, вы можете попросить SCM предоставить больше времени для отключения. Смотри: https://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.requestadditionaltime(v=vs.110).aspx Это может быть полезно во избежание страшного статуса «помечено для удаления». Максимальное значение задается в реестре, поэтому я обычно запрашиваю максимальное ожидаемое время, в течение которого поток обычно завершает работу (и никогда не превышает 12 с). См .: Какое максимальное время ожидания службы Windows для обработки запроса на остановку и как запросить дополнительное время

Мой код выглядит примерно так:

private Thread _worker;       
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); 

protected override void OnStart(string[] args)
{
    _worker = new Thread(() => ProcessBatch(_cts.Token));
    _worker.Start();             
}

protected override void OnStop()
{            
    RequestAdditionalTime(4000);
    _cts.Cancel();            
    if(_worker != null && _worker.IsAlive)
        if(!_worker.Join(3000))
            _worker.Abort(); 
}

private void ProcessBatch(CancellationToken cancelToken)
{
   while (true)
   {
       try
       {
           if(cancelToken.IsCancellationRequested)
                return;               
           // Do work
           if(cancelToken.IsCancellationRequested)
                return;
           // Do more work
           if(cancelToken.IsCancellationRequested)
                return;
           // Do even more work
       }
       catch(Exception ex)
       {
           // Log it
       }
   }
}
0 голосов
/ 07 марта 2014

Для людей, которые, как и я, ищут решение для сокращения времени закрытия, попробуйте установить значение CloseTimeout вашего ServiceHost.

Теперь я пытаюсь понять, почему без него так много времени, и я думаю, что это проблема с потоками. Я посмотрел в Visual Studio, подключился к сервису и остановил его: у меня запущены некоторые потоки, запущенные моим сервисом.

Теперь вопрос: действительно ли эти потоки заставляют мой сервис останавливаться так медленно? Разве Microsoft не подумала об этом? Вы не думаете, что это может быть проблема освобождения порта или что-то еще? Потому что это бесполезная трата времени на обработку потоков и, наконец, сокращение времени закрытия.

0 голосов
/ 07 октября 2009

Чтобы ответить на первый вопрос (почему служба будет продолжать работать в течение более 30 секунд): есть много причин. Например, при использовании WCF остановка узла приводит к тому, что процесс перестает принимать входящие запросы, и он ожидает обработки всех текущих запросов перед остановкой.

То же самое можно сказать и о других типах сетевых операций: операции будут пытаться завершиться до завершения. Вот почему большинство сетевых запросов имеют встроенное значение тайм-аута, когда запрос может «зависнуть» (сбой сервера, проблемы с сетью и т. Д.).

Без дополнительной информации о том, что именно вы делаете, невозможно точно сказать, почему это занимает 30 секунд, но, вероятно, это тайм-аут.

Чтобы ответить на второй вопрос (почему возвращается сервисный контроллер): я не уверен. Я знаю, что класс ServiceController имеет метод WaitForState, который позволяет вам ждать, пока не будет достигнуто данное состояние. Возможно, что сервисный контроллер ожидает в течение предопределенного времени (другое время ожидания) и затем принудительно завершает ваше приложение.

Также вполне возможно, что был вызван метод base.OnStop и возвращен метод OnStop, сигнализирующий ServiceController, что процесс остановлен, хотя на самом деле есть некоторые потоки, которые не остановились. Вы несете ответственность за обозначение этих тем.

0 голосов
/ 07 октября 2009

Сигнализируйте о выходе из вашего потока, сделайте его чистым и сделайте присоединение к потоку ... посмотрите, сколько времени потребуется для измерения / секундомера, когда возникают проблемы. Избегайте аварийного отключения по различным причинам.

...