Как правильно остановить многопоточную службу Windows .NET?

У меня есть служба Windows, написанная на С#, которая создает множество потоков и устанавливает множество сетевых подключений (WMI, SNMP, простой TCP, http). При попытке остановить службу Windows с помощью оснастки Services 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
}

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

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

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

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


person Mark    schedule 06.10.2009    source источник
comment
Вы вставили что-нибудь, чтобы другие потоки остановились должным образом? Являются ли они фоновыми потоками или потоками переднего плана?   -  person Jon Skeet    schedule 07.10.2009
comment
Если вы используете компоненты, которые могут внутренне создавать потоки, в идеале каждый из них должен предоставлять надлежащий механизм отключения, который вы можете вызывать в OnStop, поэтому вам не нужно напрямую управлять их потоками. Если нет, или если вы не хотите возиться с чистым выходом и просто хотите, чтобы процесс немедленно завершился, попробуйте вызвать Environment.Exit... однако я не уверен, как SCM отреагирует, когда служба завершится, пока она отправив ему команду стоп.   -  person DSO    schedule 07.10.2009


Ответы (7)


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

Есть несколько способов заставить вашу службу выйти.

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

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

person Matt Davis    schedule 07.10.2009
comment
Я принял этот ответ, потому что в нем упоминалось свойство потока IsBackground. Это единственное, что мне нужно было изменить. Я не верю в создание глобального флага, который должен использовать любой компонент — на мой взгляд, это слишком сильно связано. Однако, если потоки правильно помечены как фоновые потоки, служба нормально останавливается. - person Mark; 05.11.2009
comment
Я бы тоже не стал использовать глобальный флаг/событие. Что я сделал, так это создал оболочку вокруг объекта System.Threading.Thread. Конструктор этой оболочки создает поток, задает имя и задает свойство IsBackground. У меня есть общедоступные методы для запуска и остановки потока. В частности, метод Stop() устанавливает закрытое событие ManualResetEvent, которое сигнализирует потоку о прекращении выполнения. Чтобы сделать его полностью гибким, конструктор принимает то, что составляет делегат System.Threading.ThreadStart, позволяя любому использовать этот класс без необходимости наследовать от него. - person Matt Davis; 05.11.2009

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

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

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

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

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

person DSO    schedule 06.10.2009
comment
+1, эту проблему невозможно решить, пока вы не узнаете, что делают все ваши компоненты, и не найдете способ присоединиться (и завершить) их длительные операции, выполняемые в разных потоках. Вы можете справиться с блокировкой семафоров, установив тайм-аут для их операций ожидания, выполнив ожидание внутри цикла и проверив флаг завершения работы как условие выхода из цикла. - person Jeff Sternal; 07.10.2009

The simple way to do this may look like this:
-first crete an global event

ManualResetEvent shutdownEvent;

-at service start create the manual reset event and set it to an initial state of unsignaled

shutdownEvent = new ManualResetEvent(false);

-at service stop event

shutdownEvent.Set();

do not forget to wait for the end of the threads

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;
person lsalamon    schedule 07.10.2009

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

person rama-jka toti    schedule 06.10.2009

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

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

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

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

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

person Blue Toque    schedule 06.10.2009

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

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

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

person Chaos    schedule 07.03.2014

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

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

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

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
       }
   }
}
person Lawrence Phillips    schedule 29.09.2015