如何创建仅侦听事件的 .NET 后台服务?

How to create a .NET Background Service that only listens for events?

我需要对某些服务器上的 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);
      }
    }
  }

我发现的每个示例似乎都假设有一些周期性发生的事情(几乎总是基于计时器的事件)导致 'Task' 到 运行。我不需要那个,我只是注册了两个事件处理程序,并且服务应该只是冷静(它确实如此)直到其中一个事件发生,此时适当的 On...EntryWritten 处理程序 运行s。同样,这基本上是可行的,但感觉很笨拙。

那么,我的问题如下:

  1. 我需要在 program.cs 末尾添加“await host.RunAsync()”行吗?我不知道如何摆脱它,因为如果它不存在,服务就会死掉。
  2. 我的 ExecuteAsync 代码只是每 10 秒访问一次,什么也不做。还有什么我可以放在那里基本上说“无限期等待而不固定我的 CPU”吗?
  3. 在这种情况下设置错误处理的正确方法是什么?如果在初始化过程中出现问题(例如找不到文件),我想阻止服务启动,但如果我在构造函数中抛出错误,它似乎会像什么都没发生一样继续进行。
  4. 有没有更好的方法来解决这个问题?我想知道如果同时发生大量事件会发生什么情况 - 各种事件是在它们自己的线程中处理,还是排队等候?

提前感谢您的任何建议...

Do I need the "await host.RunAsync()" line at the end of program.cs? I can't figure out how to get rid of it, because the service just dies if it's not there.

是的。主机是创建和启动后台服务的。您仍然需要 运行 主机。

My ExecuteAsync code simply drops in for a visit every 10 seconds and does nothing. Is there something else I can put in there that essentially says "Wait indefinitely without pinning my CPU"?

如果适合您,您可以随意忽略 ExecuteAsync

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

这是基于事件的后台服务的一种思考方式:构造函数启动它,Dispose 停止它,ExecuteAsync 被忽略。

另一种观点是拥有一个最小的构造函数(通常被认为是好的设计),并让 ExecuteAsync 成为它的“主循环”。即,它在 ExecuteAsync 启动时启动,并在退出 ExecuteAsync 之前进行清理。在这种情况下,“无限”延迟是在请求关闭之前什么都不做的正常方法(通过 CancellationToken)。

What's the correct way to setup error handling in this situation? If something goes haywire during initialization (e.g. file not found), I'd like to prevent the service from starting, but if I throw an error in the constructor, it seems to proceed as if nothing happened.

你确定吗?从构造函数中抛出异常应该会阻止主机获取其托管服务列表。

Is there a better way to approach this? I wonder what happens if a burst of events happen all at once - will the various events be handled in their own thread, or do they get queued up, etc.?

这完全取决于 EventLog 的实施。我相当确定每个事件都会进入线程池线程。