首页 > 解决方案 > 如何创建仅侦听事件的 .NET 后台服务?

问题描述

我需要对某些服务器上的 Windows 事件进行一些相当精细的分析,并将它们转发到 syslog 服务器。我创建了一个运行良好的 .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);
      }
    }
  }

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

所以,我的问题如下:

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

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

标签: c#eventsservicebackground

解决方案


我是否需要 program.cs 末尾的“await host.RunAsync()”行?我不知道如何摆脱它,因为如果它不存在,服务就会死掉。

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

我的 ExecuteAsync 代码只是每 10 秒访问一次,什么也不做。还有什么我可以放在那里基本上说“无限期等待而不固定我的CPU”的东西吗?

如果这对您有用,您可以随意忽略ExecuteAsync

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

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

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

在这种情况下设置错误处理的正确方法是什么?如果在初始化过程中出现问题(例如找不到文件),我想阻止服务启动,但如果我在构造函数中抛出错误,它似乎会继续进行,就好像什么都没发生一样。

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

有没有更好的方法来解决这个问题?我想知道如果突发事件同时发生会发生什么 - 各种事件会在它们自己的线程中处理,还是会排队等?

这完全取决于EventLog. 我相当确定每个事件都会出现在线程池线程中。


推荐阅读