Как создать фоновую службу .NET, которая прослушивает только события?

#c# #events #service #background

Вопрос:

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

Вот файл Program.cs, который в значительной степени готов, но я добавил в него некоторые элементы конфигурации:

 using IHost host = Host.CreateDefaultBuilder(args)
  .UseWindowsService(options =>
  {
    options.ServiceName = "LEULogSenderSVC";
  })
  .ConfigureServices((hostContext, services) =>
  {
    IConfiguration configuration = hostContext.Configuration;
    LEULogConfig options = configuration.GetSection("LEULogConfig").Get<LEULogConfig>();
    services.AddSingleton(options);
    services.AddHostedService<LogMonitorSvc>();
  })
  .Build();

await host.RunAsync();
 

Вот файл LogMonitorSvc.cs (отредактирован для краткости):

   public sealed class LogMonitorSvc : BackgroundService
  {
    private readonly ILogger<LogMonitorSvc> _logger;
    private static LEULogConfig _options;
    private static MessageFiltering systemLogRules { get; set; }
    private static MessageFiltering applicationLogRules { get; set; }

    private static void OnApplicationEntryWritten(object source, EntryWrittenEventArgs e)
    {
      //Process Application Log Entry, optionally send to syslog...
    }

    private static void OnSystemEntryWritten(object source, EntryWrittenEventArgs e)
    {
      //Process System Log Entry, optionally send to syslog...
    }

    public LogMonitorSvc(ILogger<LogMonitorSvc> logger, LEULogConfig options)
    {
      _logger = logger;
      _options = options;

      EventLog systemLog = new EventLog("System", ".");
      EventLog applicationLog = new EventLog("Application", ".");

      systemLogRules = MessageFiltering.DeSerialize(options.SystemLogRulesFilePath);
      systemLog.EntryWritten  = new EntryWrittenEventHandler(OnSystemEntryWritten);
      systemLog.EnableRaisingEvents = true;

      applicationLogRules = MessageFiltering.DeSerialize(options.ApplicationLogRulesFilePath);
      applicationLog.EntryWritten  = new EntryWrittenEventHandler(OnApplicationEntryWritten);
      applicationLog.EnableRaisingEvents = true;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
      while (!stoppingToken.IsCancellationRequested)
      {
        //_logger.LogWarning("Worker running at: {time}", DateTimeOffset.Now);

        await Task.Delay(10000, stoppingToken);
      }
    }
  }
 

Каждый пример, который я нашел, похоже, предполагает, что периодически происходит что-то (почти всегда событие, основанное на таймере), что вызывает запуск «Задачи». Мне это не нужно, я просто регистрирую два обработчика событий, и служба должна просто остыть (что она и делает), пока не произойдет одно из этих событий, и в это время произойдет соответствующее включение…Запускается обработчик записи. Опять же, это в основном работает, но кажется довольно запутанным.

Итак, мои вопросы заключаются в следующем:

  1. Нужен ли мне «ожидающий хост.RunAsync()» строка в конце program.cs? Я не могу понять, как от этого избавиться, потому что служба просто умирает, если ее там нет.
  2. Мой код ExecuteAsync просто заходит для посещения каждые 10 секунд и ничего не делает. Есть ли что-то еще, что я могу добавить туда, что, по сути, говорит «Ждать бесконечно, не закрепляя мой процессор»?
  3. Как правильно настроить обработку ошибок в этой ситуации? Если что-то пойдет не так во время инициализации (например, файл не найден), я хотел бы предотвратить запуск службы, но если я выдам ошибку в конструкторе, кажется, что все происходит так, как будто ничего не произошло.
  4. Есть ли лучший способ подойти к этому? Интересно, что произойдет, если всплеск событий произойдет одновременно — будут ли различные события обрабатываться в их собственном потоке или они будут поставлены в очередь и т. Д.?

Заранее спасибо за любые советы…

Комментарии:

1. Мы действительно запускаем что-то подобное в производственной среде более года без каких-либо проблем до сих пор. Я бы предложил использовать await Task.Delay(-1, stoppingToken); эту блокировку на неопределенный срок, пока stoppingToken она не будет отменена. И окружая его собой try{} catch(TaskCanceledException){break;} . Но это определенно кажется банальным. Может быть, есть лучшее решение.

Ответ №1:

Нужен ли мне «ожидающий хост.RunAsync()» строка в конце program.cs? Я не могу понять, как от этого избавиться, потому что служба просто умирает, если ее там нет.

ДА. Хост — это то, что создает и запускает фоновые службы. Вам все равно нужно запустить хост.

Мой код ExecuteAsync просто заходит для посещения каждые 10 секунд и ничего не делает. Есть ли что-то еще, что я могу добавить туда, что, по сути, говорит «Ждать бесконечно, не закрепляя мой процессор»?

Вы можете смело игнорировать ExecuteAsync , если это работает для вас:

 protected override Task ExecuteAsync(CancellationToken) => Task.CompletedTask;
 

Это один из способов мышления о фоновых службах на основе событий: конструктор запускает и Dispose останавливает их, а ExecuteAsync затем игнорируется.

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

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

Ты уверен? Создание исключения из конструктора должно помешать хосту даже получить свой список размещенных служб.

Есть ли лучший способ подойти к этому? Интересно, что произойдет, если всплеск событий произойдет одновременно — будут ли различные события обрабатываться в их собственном потоке или они будут поставлены в очередь и т. Д.?

Это полностью зависит от реализации EventLog . Я совершенно уверен, что каждое событие будет происходить в потоке пула потоков.

Комментарии:

1. Спасибо за отличную информацию! Единственное, что меня все еще немного смущает, это как сделать мой конструктор более «минимальным» — как бы я переписал то, что у меня есть, чтобы сделать ExecuteAsync «основным циклом», читая файлы/создавая обработчики событий один и только один раз?

2. @user3311566: Вы бы сохранили настройку строк _logger и _options и переместили остальную часть кода в начало ExecuteAsync .