Каков правильный способ сбоя службы Windows? - PullRequest
36 голосов
/ 16 ноября 2010

Я унаследовал службу Windows, написанную на C #.В редких условиях это плохо.Тем не менее, не совсем ясно, как потерпеть неудачу.Росс Беннетт изящно излагает проблему на bytes.com .Ради простоты я просто процитирую его здесь.

Привет, народ!

Я искал это повсюду, но, похоже, не могу пошатнутьсялюбая документация из MSDN или из Google.Я просмотрел каждую статью .NET по разработке служб Windows в расположенном там MSDN.

Я занимаюсь разработкой приложения службы Windows.Эта служба считывает данные своей конфигурации из системного реестра (HKLM), где она была размещена другим приложением «менеджера».Никаких проблем там нет.

Служба использует рабочий поток для своей работы.Поток создается в OnStart () и сигнализируется / присоединяется / удаляется в OnStop ().Опять же, никаких проблем.

Все прекрасно работает, когда:

  1. Системный администратор все настроил правильно, и
  2. все ресурсы внешней сети доступны.

Но, конечно, мы, как разработчики, просто не можем положиться на:

  1. Системный администратор все настроил правильно или
  2. сторонняя сетьресурсы достижимы.

Действительно, нам нужно, чтобы приложение-служба могло каким-то образом умирать самостоятельно.Если сетевой ресурс отключается, нам нужно остановить службу.Но, что более важно, нам нужно, чтобы СКМ знал, что он остановился сам по себе.SCM должен знать, что служба "отказала" ... и не была просто кем-то закрыта.

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

Использование экземпляра ServiceController также не делает этого.Для SCM это выглядит как нормальное завершение работы, а не сбой службы.Таким образом, ни одно из действий восстановления или перезагрузки не происходит.(Также есть документация MSDNful, предупреждающая об опасностях потомка ServiceBase, использующего ServiceController, чтобы заставить вещи происходить с самим собой.)

Я читал статьи, в которых люди возились с вызовами PInvoking только для собственного кодаустановить флаг состояния «Остановлен» в SCM.Но это не останавливает процесс, в котором работает служба.

Мне бы очень хотелось узнать о предполагаемом способе:

  1. Выключение службы изнутри службыгде
  2. SCM надлежащим образом уведомлен о том, что служба «Остановлена», и
  3. Процесс исчезает из диспетчера задач.

Решения, включающие ServiceControllers don 't кажется подходящим, хотя бы потому, что 2 не выполняется.(Между прочим, документация Framework специально противопоказывает действия, которые имеют большой вес).

Буду признателен за любые рекомендации, указания на документацию или даже аргументированные предположения.:-) Ой!И я очень рад, что я упустил это.

Сердечно,

Росс Беннетт

Ответы [ 5 ]

30 голосов
/ 16 ноября 2010

В нативном коде рекомендуется вызывать SetServiceStatus с ненулевым кодом выхода, указывающим 1) он остановлен и 2) что-то пошло не так.

В управляемом коде вы могли быдобиться того же эффекта, получая дескриптор SCM через свойство ServiceBase.ServiceHandle и P / вызывая Win32 API.

Я не понимаю, почему SCM будет относиться к этому иначечем установка свойства ServiceBase.ExitCode ненулевым, а затем вызов ServiceBase.Stop, на самом деле.P / Invoke, возможно, немного более прямолинеен, если служба находится в режиме паники.

5 голосов
/ 16 ноября 2010

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

2 голосов
/ 16 ноября 2010

Я не знаю, есть ли (не P / Invoke) эквивалент для этого, но похоже, что WinAPI может вызвать SetServiceStatus со значением SERVICE_STOPPED, а затем дождаться закрытия SCM ты вниз Как положительный побочный эффект, он регистрирует сбой вашей службы в журнале событий.

Вот некоторые цитаты из соответствующей части документации :

Если служба вызывает SetServiceStatus с элементом dwCurrentState, установленным в SERVICE_STOPPED, и для элемента dwWin32ExitCode, установленным в ненулевое значение, следующая запись записывается в журнал системных событий:

[...] завершается со следующей ошибкой: [...]

Ниже приведены рекомендации по вызову этой функции:

[...]

  • Если статус SERVICE_STOPPED, выполнить всю необходимую очистку и вызвать SetServiceStatus только один раз. Эта функция делает LRPC-вызов SCM. Первый вызов функции в состоянии SERVICE_STOPPED закрывает дескриптор контекста RPC, и любые последующие вызовы могут вызвать сбой процесса.
  • Не пытайтесь выполнить какую-либо дополнительную работу после вызова SetServiceStatus с SERVICE_STOPPED, поскольку процесс обслуживания может быть прерван в любое время.

PS: По моему мнению, если сетевые ресурсы недоступны, служба не должна останавливаться, а продолжать работать в ожидании доступности ресурсов. Временные перебои в работе сети могут произойти, и они не должны требовать ручного вмешательства системного администратора после восстановления сети.

0 голосов
/ 01 ноября 2017

После некоторого тестирования я обнаружил следующие работы в тех случаях, когда вызов Stop может вызвать другие проблемы:

        ExitCode = 1;
        Environment.Exit(1);

Просто вызов Environment.Exit не заставляет SCM выполнять обработку ошибок, но сначала устанавливает ExitCode ServiceBase.

0 голосов
/ 13 августа 2014

Вы можете получить правильный ExitCode, как описано здесь .Поэтому Windows Service Manager выдаст правильный текст ошибки.

Мой OnStart в ServiceBase выглядит следующим образом:

protected override void OnStart(string[] args)
{
    try
    {
        DoStart();
    }
    catch (Exception exp)
    {
        Win32Exception w32ex = exp as Win32Exception;
        if (w32ex == null)
        {
            w32ex = exp.InnerException as Win32Exception;
        }
        if (w32ex != null)
        {
            ExitCode = w32ex.ErrorCode;
        }
        Stop();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...