Подтвердить что ты не робот

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

У меня есть служба Windows, написанная на С#, которая создает нагрузку на грузовик потоков и делает много сетевых соединений (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
}

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

ManualResetEvent shutdownEvent;

-а запуск службы создайте ручное событие reset и установите его в начальное состояние unsignaled
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;

Ответ 4

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

Ответ 5

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

Кроме того, если какая-либо из ваших задач выполняет более длительную операцию, например, вызов 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
       }
   }
}