Программа службы

92

Выбрав новый шаблон проекта C# Windows Services (Службы Windows на C#), можно приступить к созданию программы службы (Windows Service). Назначьте программе имя QuoteService.

После щелчка на кнопке ОК появится окно визуального конструктора. Однако вставлять какие-либо компоненты пользовательского интерфейса здесь нельзя, поскольку приложение такого типа не может ничего непосредственно отображать на экране. Это окно конструктора будет использоваться чуть позже для добавления других компонентов, таких как объекты установки, счетчики производительности и средства регистрации событий в журнале.

Выбор свойств новой программы Windows Service приведет к открытию окна редактора свойств, в котором для настройки доступны следующие свойства:

Autolog

Позволяет указывать, что события о запуске и останове службы должны автоматически записываться в журнал событий.

CanPauseAndContinue, CanShutdown и CanStop

Позволяют выполнять запросы на приостановку, возобновление, завершение и останов службы.

ServiceName

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

CanHandleSessionChangeEvent

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

CanHandlePowerEvent

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

По умолчанию службе назначается имя Service1, каким бы образом не назывался сам проект. Устанавливать можно только одну службу Service1. Если во время процесса тестирования получены ошибки установки, это может свидетельствовать о том, что одна служба Service1 уже установлена. В таком случае перед началом процесса развертывания в редакторе свойств имя службы должно быть изменено на более подходящее.

Изменение свойств в редакторе свойств приведет к установке соответствующих значений для унаследованного от ServiceBase класса в методе InitalizeComponent(). Этот метод уже знаком по приложениям WPF. В случае служб он применяется похожим образом.

Мастер самостоятельно сгенерирует код, но в нем нужно будет изменить имя файла на QuoteService.cs, имя пространства имен — на WinServices, а имя класса — на QuoteService. Этот код подробно рассматривается далее.

Класс ServiceBase

Класс ServiceBase является базовым для всех приложений типа Windows Service, которые разрабатываются с помощью .NET Framework. Наш класс QuoteService тоже унаследован от ServiceBase; этот класс взаимодействует с SCM за счет применения недокументированного вспомогательного класса System.ServiceProcess.NativeMethods, который служит оболочкой для вызовов API-интерфейса Win32. Этот класс является внутренним (internal), поэтому не может использоваться в разрабатываемом коде.

На рисунке приведена диаграмма последовательностей, которая иллюстрирует схему взаимодействия между SCM, классом QuoteService и классами из пространства имен System.ServiceProcess:

Взаимодействие между SCM и классами System.ServiceProcess

На этой диаграмме линии существования объектов идут в вертикальном направлении, а линии взаимодействия — в горизонтальном. Линии взаимодействия упорядочены по временному признаку сверху вниз.

Как видно на этой диаграмме, сначала SCM стартует процесс запускаемой службы. Во время запуска вызывается метод Main(), в котором происходит обращение к методу Run() базового класса ServiceBase.

Этот метод Run() регистрирует в SCM метод ServiceMainCallback() за счет применения NativeMethods.StartServiceCtrlDispatcher() и заносит соответствующую запись в журнал событий.

Далее SCM вызывает зарегистрированный метод ServiceMainCallback() в программе службы, который сам регистрирует в SCM обработчик с помощью NativeMethods.RegisterServiceCtrlHandler[Ex] () и устанавливает значение состояния службы. Затем вызывается метод OnStart(). В методе OnStart() необходимо реализовать код запуска.

В случае успешного выполнения OnStart() в журнал событий заносится запись "Service started successfully" ("Служба запущена успешно"). Реализуется обработчик в методе ServiceCommandCallback(). SCM вызывает этот метод при поступлении от службы запросов на изменение ее состояния. Метод ServiceCommandCallback() перенаправляет эти запросы дальше методам OnPause(), OnContinue(),OnStop(), OnCustomCommand() и OnPowerEvent().

Главная функция

В этом разделе рассматривается автоматически генерируемая при выборе соответствующего шаблона приложения главная функция для процесса службы. Сначала в главной функции объявляется массив классов ServiceBase по имени ServicesToRun. После этого создается один экземпляр класса QuoteService и передается этому массиву ServicesToRun в первом элементе. Если необходимо, чтобы внутри процесса данной службы запускалось более одной службы, этому массиву необходимо передать несколько экземпляров классов желаемых служб. Далее массив передается статическому методу Run() класса ServiceBase.

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

Ниже показано, как выглядит сгенерированный автоматически код:

static void Main()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[] 
            { 
                new QuoteService() 
            };
            ServiceBase.Run(ServicesToRun);
            ServiceBase.Run(new QuoteService());
        }

В случае запуска внутри процесса только одной службы массив может удаляться; метод Run() тогда принимает единственный унаследованный от ServiceBase объект и потому метод Main() может быть сокращен следующим образом:

ServiceBase.Run(new QuoteService());

Служебная программа Services.exe включает в себя множество служб. Если создается аналогичная служба, предусматривающая запуск более одной службы внутри одного и того же процесса и требующая инициализации для всех служб разделяемого состояния, такая инициализация должна производиться перед методом Run(). В методе Run() главный поток блокируется до тех пор, пока процесс службы не будет остановлен, и потому никакие следующие за ним инструкции не будут достигнуты до самого завершения работы службы.

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

Запуск службы

При запуске службы вызывается метод OnStart(). В этом методе может запускаться созданный ранее сервер сокетов. Все, что для этого требуется — это сослаться в нем на сборку QuoteServer для использования QuoteService. Поток, вызывающий метод OnStart(), не может блокироваться; этот метод должен возвращать управление тому методу, который его вызывает, каковым в данном случае является ServiceMainCallback() класса ServiceBase. После вызова метода OnStart() класс ServiceBase регистрирует обработчик и информирует SCM о том, что служба была успешно запущена:

protected override void OnStart(string[] args)
        {
            quoteServer = new QuoteServer(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "quotes.txt"), 5678);
            quoteServer.Start();

        }

Далее quoteServer объявляется приватным членом класса:

public partial class QuoteService : ServiceBase
    {
        private QuoteServer quoteServer;
        ...

Методы-обработчики

При останове службы вызывается метод OnStop(). В этом методе предоставление функциональности сервера должно прекращаться:

protected override void OnStop()
{
     quoteServer.Stop();
}

Помимо методов OnStart() и OnStop() в классе службы можно переопределить еще и следующие методы-обработчики:

protected override void OnContinue()
        {
            base.OnContinue();
        }

        protected override void OnPause()
        {
            base.OnPause();
        }

        public const int commandRefresh = 128;
        protected override void OnCustomCommand(int command)
        {
            switch (command)
            {
                case commandRefresh:
                    quoteServer.RefreshQuotes();
                    break;

                default:
                    break;
            }

        }
Пройди тесты
Лучший чат для C# программистов