#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);
}
}
}
Каждый пример, который я нашел, похоже, предполагает, что периодически происходит что-то (почти всегда событие, основанное на таймере), что вызывает запуск «Задачи». Мне это не нужно, я просто регистрирую два обработчика событий, и служба должна просто остыть (что она и делает), пока не произойдет одно из этих событий, и в это время произойдет соответствующее включение…Запускается обработчик записи. Опять же, это в основном работает, но кажется довольно запутанным.
Итак, мои вопросы заключаются в следующем:
- Нужен ли мне «ожидающий хост.RunAsync()» строка в конце program.cs? Я не могу понять, как от этого избавиться, потому что служба просто умирает, если ее там нет.
- Мой код ExecuteAsync просто заходит для посещения каждые 10 секунд и ничего не делает. Есть ли что-то еще, что я могу добавить туда, что, по сути, говорит «Ждать бесконечно, не закрепляя мой процессор»?
- Как правильно настроить обработку ошибок в этой ситуации? Если что-то пойдет не так во время инициализации (например, файл не найден), я хотел бы предотвратить запуск службы, но если я выдам ошибку в конструкторе, кажется, что все происходит так, как будто ничего не произошло.
- Есть ли лучший способ подойти к этому? Интересно, что произойдет, если всплеск событий произойдет одновременно — будут ли различные события обрабатываться в их собственном потоке или они будут поставлены в очередь и т. Д.?
Заранее спасибо за любые советы…
Комментарии:
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
.