worker是事件驱动的时候应该BackgroundService.ExecuteAsyncreturn怎么办?

What should BackgroundService.ExecuteAsync return when the worker is event-driven?

Worker Service 是在 .NET Core 3.x 中编写 Windows 服务的新方法。 worker class 扩展 Microsoft.Extensions.Hosting.BackgroundService 并实现 ExecuteAsync。该方法的文档说:

This method is called when the IHostedService starts. The implementation should return a task that represents the lifetime of the long running operation(s) being performed.

当服务完成的工作不是通常意义上的长运行操作,而是事件驱动时,这个方法return应该怎么办?例如,我正在编写一个设置 FileSystemWatcher 的服务。我如何将其封装在 Task 中?没有 Task.Never(),所以我应该 return 基于很长 Task.Delay() 的东西来防止服务关闭吗?

private async Task DoStuffAsync(CancellationToken cancel)
{
  // register events
  while(!cancel.IsCancellationRequested)
  {
    await Task.Delay(TimeSpan.FromDays(1000000), cancel);
  }
  // unregister events
}

我将延迟合并到一个名为 Eternity 的方法中:

private async Task Eternity(CancellationToken cancel)
{
    while (!cancel.IsCancellationRequested)
    {
        await Task.Delay(TimeSpan.FromDays(1), cancel);
    }
}

所以我的 ExecuteAsync 看起来像:

protected override async Task ExecuteAsync(CancellationToken cancel)
{
    using (var watcher = new FileSystemWatcher())
    {
        ConfigureWatcher(watcher);
        await Eternity(cancel);
    }
}

这似乎符合预期。

您也可以使用实际的无限延迟:

await Task.Delay(Timeout.Infinite, cancellationToken);

如果您想像 await "Eternity"await ("Eternity", token) 那样称呼它(如果您想要取消支持)。多亏了值元组,我们可以在取消支持下使用它们。

基本上你可以用一些扩展方法等待任何东西

代码如下:

protected override async Task ExecuteAsync(CancellationToken token)
        {
            using (var watcher = new FileSystemWatcher())
            {
                ConfigureWatcher(watcher);
                
                // Use any of these methods you'd like
                await "Eternity";
                await ("Eternity", token);
                await TimeSpan.FromDays(1);
                await (TimeSpan.FromDays(1), token);
            }
        }

public static class GetAwaiterExtensions 
    {
        public static TaskAwaiter GetAwaiter(this (TimeSpan, CancellationToken) valueTuple)
        {
            return Task.Delay((int) valueTuple.Item1.TotalMilliseconds, valueTuple.Item2)
                .GetAwaiter();
        }
        
        public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
        {
            return Task.Delay((int) timeSpan.TotalMilliseconds)
                .GetAwaiter();
        }
        
        public static TaskAwaiter GetAwaiter(this string value)
        {
            if (value == "Eternity")
            {
                return Task.Delay(Timeout.Infinite)
                    .GetAwaiter();
            }
            
            throw new ArgumentException();
        }
        
        public static TaskAwaiter GetAwaiter(this (string, CancellationToken) valueTuple)
        {
            if (valueTuple.Item1 == "Eternity")
            {
                return Task
                    .Delay(Timeout.Infinite, cancellationToken: valueTuple.Item2)
                    .GetAwaiter();
            }
            
            throw new ArgumentException();
        }
    }