Как создать и эксплуатировать службу Windows — от выбора между Планировщиком заданий и службой до превращения BackgroundService в службу

· Обновлено: · · Служба Windows, Windows, .NET, C#, BackgroundService, Generic Host, Фоновая обработка, Эксплуатация, Таблица решений, Техническая консультация

История изменений (1 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21619933)

Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.

Го Комура (2026). Как создать и эксплуатировать службу Windows — от выбора между Планировщиком заданий и службой до превращения BackgroundService в службу. KomuraSoft LLC. https://comcomponent.com/ru/blog/windows-service-worker-service-guide/

DOI (зарегистрированный архив)
10.5281/zenodo.21619933
DOI (последняя зарегистрированная версия)
10.5281/zenodo.21619934

«Планировщик заданий раз в 5 минут уже не успевает». «Нужен демон, который постоянно принимает данные с устройства». «Файл положили в папку — обработать за несколько секунд». Если долго консультировать по периодическому запуску, требования рано или поздно вырастают именно до этого разговора.

В этом блоге мы уже писали про автоматизацию: «Автоматизация бизнес-процессов с Power Automate» и «Надёжная эксплуатация Планировщика заданий». В главе 8 статьи о Планировщике заданий проведена граница: поллинг с интервалом в минуты или постоянное наблюдение — это уже область постоянно работающего процесса. Но как на самом деле сделать службу Windows и вывести её в эксплуатацию? Если оставить это размытым и «на всякий случай сделать службой», получаются службы, которые нельзя остановить, службы, которые упали и никто не заметил, и службы под LocalSystem, которым можно всё.

В этой статье в том порядке, в каком это решают на практике, разберём: таблицу выбора — нужна ли постоянно работающей обработке служба Windows; минимум про устройство службы (SCM — диспетчер управления службами, тип запуска, сеанс 0); реализацию на Worker Service в .NET 8; регистрацию через sc.exe и параметры восстановления; выбор учётной записи; и безопасную остановку.

1. Сначала вывод

  • Критерий выбора — периодическая обработка с интервалом от нескольких минут: Планировщика заданий достаточно. Постоянное ожидание (сокеты, именованные каналы, FileSystemWatcher), реакция за секунды или автоматическое восстановление после падения — это служба Windows.
  • UI показать нельзя — начиная с Windows Vista служба работает в сеансе 0 и не может напрямую взаимодействовать с пользователем (не может показать UI). Если UI нужен, службу и UI-приложение разделяют и связывают межпроцессным взаимодействием (IPC).1
  • Как реализовать — официальный путь в .NET: шаблон Worker Service плюс AddWindowsService из Microsoft.Extensions.Hosting.WindowsServices (для семейства IHostBuilderUseWindowsService). Тот же exe запускается как обычная консоль, поэтому разработка и отладка заметно проще, чем у классических служб.2
  • Исключения и перезапуск — начиная с .NET 6 необработанное исключение внутри BackgroundService по умолчанию останавливает хост (BackgroundServiceExceptionBehavior.StopHost). Это штатная остановка, поэтому перезапуск через параметры восстановления SCM не срабатывает. Сбой, после которого нужен перезапуск, завершайте процесс с ненулевым кодом.32
  • Учётная запись — не берите LocalSystem по инерции. У sc.exe create по умолчанию как раз LocalSystem: если не задать явно, служба стартует с максимальными правами.4 Для чисто локальной работы — LocalService или виртуальная учётная запись; если в домене нужны общие папки или БД — gMSA (group Managed Service Account, групповая управляемая учётная запись службы: пароль автоматически ведёт контроллер домена).5
  • Восстановление и остановка — параметры восстановления (sc.exe failure) и остановку (HostOptions.ShutdownTimeout, по умолчанию 30 секунд6) закладывают уже при регистрации. «Служба, которая не реагирует на остановку» — то, чего эксплуатация ненавидит больше всего.

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 19, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Когда нужна служба — таблица выбора: Планировщик заданий, служба, фоновое приложение

Когда появляется требование «вроде как должно работать постоянно», вариантов три: Планировщик заданий, служба Windows и «фоновое приложение в автозагрузке» (то, что живёт в области уведомлений). Сопоставим свойства.

Аспект Планировщик заданий Служба Windows Фоновое приложение (автозагрузка)
Когда запускается По времени или событию, каждый раз заново С загрузки ОС (до входа пользователя), постоянно С входа пользователя
Нужен ли вход в систему Можно обойтись (неинтерактивный запуск) Не нужен Нужен (после выхода пропадает)
UI Нельзя (в неинтерактивной конфигурации) Нельзя (сеанс 0) Можно (область уведомлений, диалоги)
Постоянно или по расписанию По расписанию (от часа до суток — как раз её место) Постоянно Постоянно (но внутри сеанса пользователя)
Права Учётная запись задаётся для каждой задачи Учётная запись службы (глава 6) Права вошедшего пользователя
Наблюдение и восстановление Журнал плюс свои уведомления Параметры восстановления SCM (автоперезапуск) Нет (делать самим)
Стоимость развёртывания Регистрация через XML / PowerShell sc.exe / установщик Запись в ключ Run и т. п.

Ось выбора такая.

  • Периодическая обработка от нескольких раз в день до раза в час, без состояния между запусками — Планировщик заданий. Делать из этого службу и самим крутить таймер избыточно: эксплуатация, описанная в «статье о Планировщике заданий», обойдётся заметно дешевле.
  • Если суть — постоянное ожидание — приём запросов по TCP или каналу, наблюдение за папкой через FileSystemWatcher, последовательная обработка очереди, реакция за секунды — это служба Windows. Если вы начинаете дробить работу на «задачу каждые 5 минут» с поллингом, вы заново и хуже реализуете постоянно работающий процесс.
  • Если основа — UI (управление из области уведомлений, всплывающие окна пользователю) — это фоновое приложение. Оно не работает без входа, поэтому для серверных сценариев не годится. Если «фон нужен постоянно, но нужен и UI», разнесите это на два процесса — службу и UI-приложение, как в следующей главе.

Есть ещё один критерий, который часто пропускают, — восстановление. Задание Планировщика после сбоя просто ждёт следующего запуска по расписанию. У службы автоперезапуск берёт на себя SCM (глава 5). Требование «даже если упадёт ночью, к утру должно подняться само» само по себе уже повод делать службу.

3. Минимум про устройство службы — SCM, тип запуска, сеанс 0

3.1 SCM и тип запуска

Центральный компонент служб Windows — диспетчер управления службами (SCM). Он ведёт реестр служб, принимает запросы на запуск и остановку и выполняет восстановление при сбое. Процесс службы должен разговаривать с SCM по установленному протоколу, но эту часть берут на себя библиотеки .NET (глава 4). Нам как проектировщикам остаются четыре решения: тип запуска, учётная запись, восстановление и остановка.

Тип запуска — из четырёх вариантов.4

Тип запуска Поведение Когда использовать
Автоматически (auto) Запускается при загрузке ОС Базовый вариант для постоянно работающей службы
Автоматически (отложенный запуск) (delayed-auto) Запускается чуть позже других автоматических служб Первый выбор для прикладных служб: меньше давки сразу после старта и меньше шансов, что зависимости ещё не поднялись
Вручную (demand) Только по запросу Вспомогательные службы, которые стартует другое приложение
Отключена (disabled) Запуск невозможен Вывод из эксплуатации, блокировка

Для прикладных служб отложенный запуск стоит сделать значением по умолчанию. Сразу после загрузки ОС ни сеть, ни БД, ни другие службы ещё не готовы: при самом быстром «Автоматически» первое подключение часто срывается. Устойчивее двухступенчатая схема: отложенный запуск даёт паузу, а сбои подключения гасятся повторами внутри ExecuteAsync, о которых ниже.

Ещё одно, что нужно знать, — тайм-аут запуска. SCM не будет бесконечно ждать, пока служба сообщит, что запуск завершён. Если превысить тайм-аут по умолчанию (ServicesPipeTimeout, 30 секунд), пишутся события 7000 / 7011, и запуск считается неудачным.7 То есть тяжёлую инициализацию — повторы подключения к БД, построение большого кэша — нельзя делать внутри «процедуры запуска». Жёсткое правило: запуск должен завершаться сразу, тяжёлую работу ведут в основном цикле (ExecuteAsync).

Если сложить разговор SCM и процесса службы на одну схему, получается такой поток. И запуск, и остановка — это обмен «запрос» ↔ «сообщение о завершении», и у этого обмена есть лимит времени: в этом особенность службы.

BackgroundServiceпроцесс службыSCMАдминистратор / загрузка ОСBackgroundServiceпроцесс службыSCMАдминистратор / загрузка ОСЕсли за 30 с ServicesPipeTimeout по умолчаниюне сообщить, что служба выполняется, — события 7000 / 7011Если превысить ShutdownTimeout (по умолчанию 30 с),остановку форсируют, не дожидаясь завершения очисткизапрос запуска sc.exe start или автозапускзапускает процесссообщает START_PENDING (идёт запуск)сообщает SERVICE_RUNNING (выполняется)запускает ExecuteAsyncцикл ожидания и обработкизапрос остановки sc.exe stop или завершение работы ОСпередаёт запрос остановкиотменяет stoppingTokenвыходит из цикла, StopAsync завершаетсясообщает SERVICE_STOPPED (остановлена)

Именно этот обмен в классической разработке службы писали сами. В .NET его берёт на себя lifetime, который подключает AddWindowsService (глава 4). Нам остаются тело ExecuteAsync и реакция на запрос остановки (глава 7).

3.2 Изоляция сеанса 0 — служба не может показать UI

Начиная с Windows Vista служба работает в изолированном сеансе 0 и не может напрямую взаимодействовать с пользователем.1 Вызов MessageBox.Show или показ формы внутри службы ничего не выведет на экран вошедшего пользователя. Хуже того, это классическая причина зависания: процесс будет ждать кнопку OK, которую никто не нажмёт. Когда переносите старый код в службу, обязательно проверьте, не остались ли окна, задуманные как показ ошибки.

Почему сеансы разделены, в какой сеанс вы попадаете по RDP, пересекают ли именованные объекты границы сеанса — само устройство сеансов разобрано в «Как устроена изоляция сеансов Windows». Этой статье нужно одно: «служба не может показать UI». Дальше читайте с этим в уме.

Если UI нужен, правильная схема — разнести процессы: служба (сеанс 0) и UI-приложение (сеанс пользователя), связать их межпроцессным взаимодействием (IPC). Так рекомендует сама Microsoft: IPC вроде именованного канала, UI-сторона возвращает результаты службе.1 Какой механизм IPC выбрать, разобрано в парной статье того же дня — «Таблица выбора межпроцессного взаимодействия». Если UI-приложение тоже посадить на Generic Host, оба процесса получат одинаковый каркас DI, логирования и конфигурации (см. «Generic Host и BackgroundService в классических приложениях»).

4. Как сделать службу в .NET — Worker Service и AddWindowsService

4.1 Шаблон и Program.cs

Разработку службы в .NET начинают с шаблона Worker Service. Он входит в SDK, поэтому dotnet new worker даёт заготовку; остаётся добавить пакет Microsoft.Extensions.Hosting.WindowsServices и вызвать AddWindowsService — и приложение готово работать как служба Windows.2 Устройство Generic Host, на котором это стоит, разобрано в «Что такое .NET Generic Host».

using App.MonitorService;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Подключает lifetime, который при запуске как службы Windows общается с SCM.
// Если процесс запущен как консоль, остаётся обычный ConsoleLifetime
builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "KsMonitor";
});

builder.Services.AddSingleton<MeasurementQueue>();
builder.Services.AddHostedService<MonitorWorker>();

IHost host = builder.Build();
host.Run();

Если проект ещё на старом стиле с Host.CreateDefaultBuilder, вместо этого вызывают расширение IHostBuilderUseWindowsService(). Роль та же.

Есть ловушка того же типа, что и с рабочим каталогом («Начать в») в статье о Планировщике заданий. Текущий каталог при запуске как службы — C:\Windows\System32. Если appsettings.json и подобные файлы читаются по относительному пути, получится «в консоли работает, а служба настройки не видит». Разрешайте файлы конфигурации относительно исполняемого файла либо, как советует официальное руководство, передайте --contentRoot в binpath при регистрации и явно задайте корень контента.2

4.2 ExecuteAsync — stoppingToken и исключения

Основную логику пишут в ExecuteAsync класса, унаследованного от BackgroundService. Здесь по сути два решения: как отвечать на запрос остановки и что делать с исключениями.

namespace App.MonitorService;

public sealed class MonitorWorker(
    MeasurementQueue queue,
    ILogger<MonitorWorker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    // Берём один элемент из очереди и обрабатываем. Токен передаём и в ожидание
                    var item = await queue.DequeueAsync(stoppingToken);
                    await ProcessAsync(item, stoppingToken);
                }
                catch (Exception ex) when (ex is IOException or TimeoutException)
                {
                    // Восстановимый сбой: пишем в лог и повторяем попытку с паузой
                    logger.LogError(ex, "Обработка не удалась. Повтор через 30 секунд.");
                    await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
                }
            }
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
            // Запрос остановки от SCM. Штатный путь, ничего не делаем.
            // when ограничивает это именно stoppingToken, чтобы отмену
            // из тайм-аута внутренней операции не принять за «штатную остановку»:
            // иначе воркер тихо встанет, а хост продолжит жить
        }
        catch (Exception ex)
        {
            // Непредвиденный сбой: при StopHost по умолчанию хост останавливается
            // «штатно», и параметры восстановления SCM (автоперезапуск) не срабатывают.
            // Завершаемся с ненулевым кодом, чтобы SCM увидел сбой
            logger.LogCritical(ex, "Неустранимая ошибка — служба завершает работу.");
            Environment.Exit(1);
        }
    }

    private Task ProcessAsync(Measurement item, CancellationToken token)
        => throw new NotImplementedException();
}
  • Передавайте stoppingToken во все ожидания. Task.Delay, ввод-вывод, ожидание очереди. Даже одно длинное ожидание без токена становится рассадником «службы, которая не реагирует на остановку» (глава 7).
  • Отделяйте восстановимые сбои от неустранимых. Обрыв сети и разовый тайм-аут ловите в цикле, пишите в лог и повторяйте попытку. catch (Exception), который только глотает ошибку и делает continue, даёт службу, которая тихо крутится вхолостую. Как классифицировать сбой, разобрано в «Завершать работу или продолжать при непредвиденном исключении».
  • Знайте поведение по умолчанию для необработанного исключения. До .NET 6 исключение, вышедшее из ExecuteAsync, пропадало без следа, и служба становилась «зомби»: снаружи работает, внутри ничего не делает. Начиная с .NET 6 по умолчанию действует BackgroundServiceExceptionBehavior.StopHost: исключение пишется в лог, затем хост останавливается.3 Но эта остановка штатная, поэтому даже при настроенных параметрах восстановления перезапуска не будет. Сбой, который должен поднимать службу через параметры восстановления, явно завершайте аварийно через Environment.Exit(1), как в коде выше, — так же сказано в официальном руководстве.2

4.3 Тот же exe работает как консоль — главная причина, почему разработка стала проще

AddWindowsService переключает lifetime на служебный только когда процесс реально запущен как служба Windows. То есть тот же exe при F5 в Visual Studio или dotnet run работает как обычное консольное приложение. Точки останова ставятся, Ctrl+C прогоняет весь путь от запроса остановки до StopAsync локально.

Но «в консоли завелось» не значит «как служба тоже заведётся». Три вещи — другая учётная запись (глава 6), сеанс 0 и текущий каталог — нужно проверить именно как службу на реальной машине. Причина «в консоли работает, как служба — нет» почти всегда сводится к этим трём пунктам.

5. Регистрация и эксплуатация — sc.exe, параметры восстановления, журнал событий

5.1 Регистрация через sc.exe create

Опубликованный (dotnet publish; удобнее публикация в один файл) exe регистрируют в SCM через sc.exe create. Запускают из PowerShell от имени администратора.

sc.exe create "KsMonitor" binpath= "C:\Services\KsMonitor\KsMonitor.exe" start= delayed-auto obj= "NT AUTHORITY\LocalService" displayname= "KS: служба приёма измерительных данных"
sc.exe description "KsMonitor" "Постоянно работающая служба: принимает измерительные данные и записывает их в БД (ответственный: отдел информационных систем)"

Сразу про классическую ловушку. После знака равенства в binpath= и start= нужен пробел. До знака равенства — имя параметра, пробел перед значением синтаксически обязателен (без него команда падает).4 Если опустить obj=, по умолчанию берётся LocalSystem.4 Зарегистрировать «не глядя» — значит стартовать с максимальными правами, поэтому выбор из главы 6 делают уже при регистрации.

description выглядит мелочью, но важен. Если через несколько лет кто-то откроет services.msc и поймёт, что это за служба, можно ли её останавливать и к кому идти, — будущие разборы станут дешевле. Удаление — после остановки: sc.exe delete "KsMonitor".2

Если машин несколько или файлы регулярно меняют, как можно раньше уходите от ручного sc.exe к установщику (MSI, WiX ServiceInstall и т. п.), который берёт регистрацию, обновление и удаление одним комплектом. Ручная инструкция по регистрации рано или поздно породит машину, на которой один шаг пропустили.

5.2 Параметры восстановления — «упала — перезапусти» отдайте SCM

Одно из главных преимуществ службы — штатные параметры восстановления SCM. Поведение при аварийном завершении процесса задают декларативно.2

sc.exe failure "KsMonitor" reset= 86400 actions= restart/60000/restart/60000/restart/300000

В этом примере: «1-й и 2-й сбой — перезапуск через 60 секунд, с 3-го — через 5 минут, счётчик сбоев сбрасывается через 24 часа». То же самое можно выставить в GUI (services.msc → свойства → вкладка «Восстановление»). Прежде чем писать свою «сторожевую задачу» или скрипт мониторинга, используйте эту штатную возможность.

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

5.3 Журнал событий и файловый журнал вместе

На Windows Host.CreateApplicationBuilder сам добавляет провайдер логирования EventLog. И в журнал событий по умолчанию попадает только Warning и выше.2 Легко решить, что «после превращения в службу все Information пропали», но они не пропали: их отсекает фильтр журнала событий по умолчанию. Если нужны и эксплуатационные события вроде запуска и остановки, явно задайте уровень провайдера EventLog в appsettings.json.

{
  "Logging": {
    "EventLog": {
      "SourceName": "KsMonitor",
      "LogLevel": {
        "Default": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    }
  }
}

Один важный момент. В источник событий нельзя писать, пока его заранее не зарегистрировали, а для регистрации нужны права администратора. Пропуск SourceName не спасает: по умолчанию AddWindowsService берёт имя приложения как имя источника2, так что вы всё равно стартуете с «незарегистрированного источника». Учётная запись с минимумом прав вроде LocalService не может создать источник во время выполнения; если создание не удалось, эксплуатация начинается без единой записи в журнале событий. Регистрацию делают на стороне установщика (или в процедуре настройки с правами администратора). Та же мысль, что в главе 6 «статьи о Планировщике заданий»: «CreateEventSource выносят в установку».

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

5.4 Проверить, что служба зарегистрировалась — куда смотреть в командах и в GUI

После регистрации по очереди смотрят три места: «стоит ли то, что задумали, и под той ли учётной записью работает».

Конфигурация (команды). sc.exe qc "KsMonitor" печатает регистрацию. Смотреть три строки: BINARY_PATH_NAME (указывает ли на опубликованный exe), START_TYPE (автоматически / отложенный автозапуск / вручную), SERVICE_START_NAME (учётная запись из главы 6). Состояние — STATE в sc.exe query "KsMonitor"; в PowerShell — Get-Service KsMonitor. Текущие параметры восстановления читает sc.exe qfailure "KsMonitor".

Конфигурация (GUI). Win+R → «services.msc» открывает «Службы» (тот же экран: «Управление компьютером» → «Службы и приложения» → «Службы»). Правый щелчок по службе → «Свойства»: вкладка «Общие» — тип запуска и описание из 5.1, «Вход в систему» — учётная запись, «Восстановление» — действия при 1-м, 2-м и последующих сбоях.

Журнал событий. Win+R → «eventvwr.msc» открывает Просмотр событий; «Журналы Windows» → «Система», источник «Service Control Manager». Здесь остаются событие 7000 / 7011, когда запуск не успел сообщить о завершении7, и события 7031 (если есть действие восстановления) и 7034 (если действия нет), когда служба завершилась аварийно8. Собственные логи приложения из 5.3 идут в другое место: у провайдера EventLog назначение по умолчанию — «Application»9, поэтому их ищут в «Журналы Windows» → «Приложение» по имени источника (SourceName, по умолчанию имя приложения2). Жива ли служба — в «Системе», что происходит внутри приложения — в «Приложении». Так при сбое не приходится бродить по журналам.

6. Учётная запись — не берите LocalSystem по инерции

Служба работает в контексте безопасности указанной учётной записи: при запуске SCM входит под ней и вешает на процесс токен.10 Это версия для служб той же темы «от чьего имени выполняется процесс», что в главе 3 статьи о Планировщике заданий. Варианты:

Учётная запись Права Кем представляется в сети Пароль Когда использовать
LocalSystem Очень широкие (на уровне ОС) Учётная запись компьютера Не нужен Как правило избегать. Это значение по умолчанию у sc.exe create
LocalService Минимум Анонимно (к общим ресурсам доступа нет) Не нужен Первый кандидат для чисто локальной обработки
NetworkService Минимум Учётная запись компьютера Не нужен Лёгкая работа с ресурсами домена
Виртуальная учётная запись (NT SERVICE\имя-службы) Только выданные права Учётная запись компьютера Не нужен ACL можно выдать на конкретную службу. Минимум прав на локальные ресурсы
gMSA Только выданные права Сама gMSA Автоматически ведёт контроллер домена Основной выбор для служб, которым в домене нужны общие папки или БД

Три опорные точки.

  • Не берите LocalSystem только потому, что «так работает». Дыра в службе сразу становится захватом всей машины. Нужны ли на самом деле права уровня администратора, можно разобрать по «Когда действительно нужны права администратора». Часто нужна лишь запись в конкретную папку — тогда достаточно выдать ACL на папку виртуальной учётной записи (NT SERVICE\KsMonitor). Виртуальную учётную запись не создают и паролем не управляют.5
  • Ловушка сетевых общих папок. LocalService в сети — аноним, поэтому доступ к \\server\share не пройдёт. NetworkService, виртуальные учётные записи и LocalSystem выходят в сеть как учётная запись компьютера (DOMAIN\имя-машины$)5, поэтому на стороне общего ресурса разрешения — и на общий доступ, и NTFS — нужно выдать именно учётной записи компьютера. «Пользователю дали доступ, а читает только служба не может» почти всегда объясняется этим. Сначала решите, «от чьего имени выходите в сеть», и только потом настраивайте общий ресурс.
  • Если запускаете под обычной пользовательской учётной записью, закладывайте и работу с паролем. Для выделенной учётной записи нужно право «Вход в качестве службы», а когда пароль истечёт, вход не выполнится и служба не стартует.10 Это версия для служб проблемы «после смены пароля задача тихо умерла». В домене правильный ответ — убрать проблему целиком через gMSA, чей пароль ведёт домен (тот же вывод, что в разделе 3.2 статьи о Планировщике заданий).

7. Безопасная остановка — ShutdownTimeout и незавершённая обработка

Поток остановки. Когда services.msc, sc.exe stop или завершение работы ОС заставляют SCM выдать запрос на остановку, служебный lifetime в .NET переводит это в остановку хоста (StopApplication), stoppingToken отменяется, ExecuteAsync выходит, вызывается StopAsync каждой службы. Сколько хост ждёт всю эту цепочку, задаёт HostOptions.ShutdownTimeout, по умолчанию 30 секунд.611 Если не уложиться, остановку форсируют, не дожидаясь завершения очистки.

Если 30 секунд не хватает, чтобы дописать одну незавершённую операцию, увеличьте тайм-аут, отталкиваясь от нужного времени.

builder.Services.Configure<HostOptions>(options =>
{
    // От максимального времени, чтобы дописать текущую операцию, отсчитываем тайм-аут
    options.ShutdownTimeout = TimeSpan.FromSeconds(90);
});

После этого проектирование остановки сводится к трём пунктам.

  • Заранее зафиксируйте порядок действий после запроса остановки. Перестать принимать новую работу → довести незавершённую обработку (или оборвать в безопасной точке и записать так, чтобы можно было продолжить) → закрыть соединения и убрать временные файлы. Без этого порядка получаются две крайности: бросить всё в момент токена или пытаться доделать всё и не уложиться во время.
  • Не делайте «службу, которая не реагирует на остановку». Достаточно одного длинного цикла или ожидания без токена, чтобы служба зависла в SCM в состоянии «останавливается». Это блокирует перезагрузку Windows Update и на каждой плановой остановке сервера требует ручного kill — один из самых ненавистных дефектов в эксплуатации. Как в главе 4, проверяйте токен на каждой границе обработки и передавайте CancellationToken в любой длительный ввод-вывод. В консоли этот поток остановки можно прогнать через Ctrl+C, поэтому в тесты перед релизом стоит включить пункт «за сколько секунд после Ctrl+C процесс завершается».
  • Важнее аккуратной процедуры остановки — конструкция, которая не ломается даже при kill. Отключение питания и принудительное завершение случаются, как бы тщательно вы ни проектировали остановку. Транзакции, атомарная запись файлов, единицы работы, которые можно прогнать повторно, — основа: «даже если процесс умер посередине, при следующем запуске согласованность восстанавливается». Аккуратная остановка — вопрос хорошего тона.

8. Итог

Решение простое. Периодическая обработка — Планировщик заданий; постоянное ожидание, реакция за секунды или автовосстановление — служба Windows; если основа — UI, фоновое приложение. Если решили делать службу, сегодняшний стандартный путь — Worker Service и AddWindowsService в .NET: «разрабатывать как консоль, поставлять как службу».

При регистрации нужно заложить четыре решения: тип запуска (для прикладных служб — отложенный), учётную запись (не LocalSystem, а виртуальная учётная запись или gMSA), параметры восстановления (sc.exe failure в паре с Environment.Exit(1)) и остановку (ShutdownTimeout и завершение незавершённой обработки). Если проработать эти четыре пункта и писать ExecuteAsync с уважением к stoppingToken, служба Windows перестаёт быть «чем-то особенно сложным». И наоборот, служба, которую запустили, не решив эти четыре пункта, через несколько лет вернётся тремя проблемами сразу: её нельзя остановить, никто не замечает, когда она падает, и прав слишком много, чтобы к ней подступиться. Чинить существующую службу тоже быстрее всего начинать со сверки именно этих четырёх пунктов.

Связанные статьи

Смежные темы консультаций

KomuraSoft LLC делает ревью проектирования постоянно работающей обработки (включая решение, переходить ли с Планировщика заданий) и консультирует по разбору и восстановлению служб Windows, которые «не реагируют на остановку» или «падают незаметно».

Источники

  1. Microsoft Learn, Interactive Services. Начиная с Windows Vista служба не может напрямую взаимодействовать с пользователем, выполняется в сеансе 0; если нужен UI, рекомендуют отдельный процесс GUI-приложения, связанный со службой через IPC.  2 3

  2. Microsoft Learn, Create Windows Service using BackgroundService. Официальное руководство по превращению Worker Service в службу Windows: AddWindowsService, уровень Warning по умолчанию у провайдера EventLog, регистрация, восстановление и удаление через sc.exe, и то, что параметрам восстановления нужен ненулевой код завершения.  2 3 4 5 6 7 8 9 10

  3. Microsoft Learn, .NET 6 breaking change: Exception handling in hosting. Начиная с .NET 6 необработанное исключение в BackgroundService.ExecuteAsync пишется в лог и по умолчанию останавливает хост (BackgroundServiceExceptionBehavior.StopHost).  2

  4. Microsoft Learn, sc.exe create. Спецификация start= (auto / demand / delayed-auto и др.) и obj= (по умолчанию LocalSystem); после знака равенства параметра обязателен пробел (без него команда падает).  2 3 4

  5. Microsoft Learn, Service Accounts in Windows Server. Виртуальная учётная запись (NT SERVICE\имя-службы) ходит в сеть с учётными данными компьютера без управления паролем; пароль gMSA автоматически ведёт домен.  2 3

  6. Microsoft Learn, .NET Generic Host. Поток завершения хоста и то, что при остановке ожидание идёт в пределах настраиваемого тайм-аута (по умолчанию 30 секунд).  2

  7. Microsoft Learn, A service does not start, and events 7000 and 7011 are logged. SCM ждёт запуска службы только время ServicesPipeTimeout; при превышении пишет события 7000 / 7011. Также процедура увеличения тайм-аута по умолчанию.  2

  8. Microsoft Learn (архив), Event ID 7031 — Service Stop Operations и Event ID 7034 — Service Stop Operations. SCM фиксирует неожиданное завершение службы и то, что после действия восстановления перезапустить не удалось. Событие 7031 (символ EVENT_SERVICE_CRASH) — сообщение вида «служба неожиданно завершилась. Это N-й раз. Следующее действие восстановления будет выполнено через M мс»; 7034 (символ EVENT_SERVICE_CRASH_NO_ACTION) — без описания действия восстановления. Источник обоих — Service Control Manager. Действие восстановления меняют на вкладке «Восстановление» в свойствах оснастки «Службы». 

  9. Microsoft Learn, EventLogSettings.LogName Property. Имя журнала событий, в который пишет провайдер EventLog; если не задано, по умолчанию “Application”. 

  10. Microsoft Learn, Service User Accounts. Служба выполняется в контексте безопасности указанной учётной записи; SCM при запуске выполняет вход; если пароль истёк, служба не стартует. Специальные учётные записи LocalService / NetworkService / LocalSystem.  2

  11. Microsoft Learn, HostOptions.ShutdownTimeout Property. Свойство, которое задаёт тайм-аут по умолчанию для StopAsync

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Как выбрать между Планировщиком заданий и службой Windows?
Если это периодическая обработка с интервалом от нескольких минут и без состояния между запусками, Планировщика заданий достаточно. Если в требованиях есть постоянное ожидание по TCP или именованному каналу, наблюдение за папкой через FileSystemWatcher, реакция за секунды или автоматическое восстановление после падения — нужна служба Windows. Если вы начинаете дробить работу на «задачу каждые 5 минут» с поллингом, это признак, что постоянно работающий процесс вы заново и хуже реализуете сами. Если же основа — UI (управление из области уведомлений и т. п.), есть третий вариант: фоновое приложение, которое живёт только пока пользователь в системе.
Может ли служба Windows показать UI (окно)?
Нет. Начиная с Windows Vista служба работает в изолированном сеансе 0 и не может напрямую взаимодействовать с пользователем. Вызов MessageBox.Show внутри службы ничего не покажет на экране пользователя: процесс будет ждать кнопку OK, которую никто не нажмёт, и на этом зависнет. Если UI нужен, правильное решение — разнести службу и UI-приложение по процессам и связать их межпроцессным взаимодействием, например именованным каналом. Когда переносите старый код в службу, обязательно проверьте, не остались ли окна с сообщениями об ошибках.
Как в .NET сделать службу Windows?
Официальный путь — шаблон Worker Service (dotnet new worker), пакет Microsoft.Extensions.Hosting.WindowsServices и вызов AddWindowsService. Тот же exe при F5 в Visual Studio или dotnet run работает как обычное консольное приложение, поэтому разработка и отладка заметно проще. Регистрация — через sc.exe create, но есть две особенности: после знака равенства в binpath= и start= обязателен пробел, а если опустить obj=, по умолчанию берётся LocalSystem (максимальные права). Текущий каталог при запуске службы — C:\Windows\System32, поэтому файлы настроек нужно разрешать относительно исполняемого файла.
Что будет со службой, если в BackgroundService вылетело исключение?
Начиная с .NET 6 необработанное исключение, вышедшее из ExecuteAsync, по умолчанию (BackgroundServiceExceptionBehavior.StopHost) пишется в лог, после чего хост останавливается. Это считается штатной остановкой, поэтому параметры восстановления SCM (автоперезапуск) не срабатывают. Сбой, после которого нужен перезапуск, надо явно завершать аварийно с ненулевым кодом — например, Environment.Exit(1). Восстановимые сбои вроде обрыва сети ловите в цикле, пишите в лог и повторяйте попытку: их ещё на этапе проектирования отделяют от неустранимых.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог