2009-10-06 1 views
21

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

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

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

Вот код в 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 вернется, чтобы процесс фактически остановился.

+0

ли вы положили в чем-нибудь, чтобы другие потоки остановить надлежащим образом? Являются ли они фоновыми потоками или потоками переднего плана? –

+3

Если вы используете компоненты, которые могут внутренне создавать потоки, в идеале каждый из них будет выставлять надлежащий механизм выключения, который вы можете вызывать в OnStop, поэтому вам не нужно напрямую управлять потоками. Если нет, или если вы не хотите беспокоиться о чистом выходе и просто хотите, чтобы процесс был немедленно завершен, попробуйте вызвать среду. Exit ... однако я не уверен, как SCM будет реагировать, когда служба завершается во время он отправляет команду остановки. – DSO

ответ

15

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

Существует несколько способов заставить ваш сервис выйти.

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

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

+0

Я принял этот ответ, потому что он упомянул свойство потока IsBackground. Это было единственное, что мне нужно было изменить. Я не верю в создание глобального флага, который должен использовать любой компонент, - на мой взгляд, это слишком много. Однако, если потоки правильно помечены как фоновые потоки, тогда служба прекращает работу. – Mark

+1

Я бы тоже не использовал глобальный флаг/событие. То, что я сделал, создается оберткой вокруг объекта System.Threading.Thread. Конструктор этой оболочки создает поток, задает имя и устанавливает свойство IsBackground. У меня есть общедоступные методы для запуска и остановки потока. Метод Stop(), в частности, устанавливает частный ManualResetEvent, который сигнализирует о прекращении работы потока. Чтобы сделать его полностью гибким, конструктор принимает то, что представляет собой делегат System.Threading.ThreadStart, позволяющий любому использовать этот класс, не наследуя его. –

10

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

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

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

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

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

+1

+1, вам не удастся решить эту проблему, пока вы не узнаете, что делают все ваши компоненты, и есть способ присоединиться (и прекратить) их длительные операции, выполняемые на разных потоках.Вы можете иметь дело с блокировкой семафоров, устанавливая тайм-аут в своих операциях Wait, выполняя ожидание внутри цикла и проверяя ваш флаг выключения как условие выхода цикла. –

0

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

0

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

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

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

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

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

1

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

ManualResetEvent shutdownEvent;

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

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

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

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

0

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

Кроме того, если какая-либо из ваших задач выполняет более длительную операцию, такую ​​как вызов sproc, и поэтому ваш тайм-аут соединения должен быть немного дольше, вы можете попросить SCM больше времени на остановку. См.: https://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.requestadditionaltime(v=vs.110).aspx Это может быть полезно для предотвращения страшного статуса «Маркировка для удаления». Максимум устанавливается в реестре, поэтому я обычно запрашиваю максимальное ожидаемое время, в течение которого поток обычно закрывается (и не более 12 с). См: what is the maximum time windows service wait to process stop request and how to request for additional time

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

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 
     } 
    } 
}