在 .NET Core 5 的托管服务的 StartAsync 和 StopAsync 中 return 是什么?

What to return in StartAsync and StopAsync of a hosted service for .NET Core 5?

尝试大致遵循 MSDN,我在 StartUp class.

中的范围服务之后添加了托管服务
public void ConfigureServices(IServiceCollection services)
{
  ...
  services.AddScoped<IUtilityService, UtilityService>();
  services.AddHostedService<StartupService>();
  ...
}

我已经实现了 StartAsync 这样的。

public class StartupService : IHostedService
{
  private IServiceProvider Provider { get; }
  public StartupService(IServiceProvider provider)
  {
    Provider = provider;
  }

  public Task StartAsync(CancellationToken cancellationToken)
  {
    IServiceScope scope = Provider.CreateScope();
    IUtilityService service = scope.ServiceProvider
      .GetRequiredService<IUtilityService>();

    service.Seed();

    return Task.CompletedTask;
  }

  public Task StopAsync(CancellationToken cancellationToken)
  {
    throw new NotImplementedException();
  }
}

我已经阅读了很多文章和博客,但我无法理解在方法末尾应该 return 编辑什么。它现在似乎工作,但我可以清楚地看到我通过不使用异步调用和 returninig 一个虚拟(甚至不是停止!)来破坏这个想法,所以我可以安全地得出结论,我正在做它错了(虽然不是很明显,但我相信它会在未来咬我的屁股)。

我应该 return 在实施中做什么以确保我“使用”框架而不是反对框架?

我有一个包含许多服务的服务框架,我也可以在 Web 面板中看到每个服务的状态。所以,在我的解决方案中:

StartAsync方法中,我初始化并启动所有作业,所以系统等待作业完成,完成后,我return Task.CompletedTask

StopAsync 中,我尝试停止所有作业并确保它们已成功停止,然后 return Task.CompletedTask

StartAsync 需要 return 一个任务,它可能是也可能不是 运行ning(但理想情况下它应该是 运行ning,这就是 HostedService 的重点 - operation/task 运行 在应用程序的生命周期内,或者只是比正常情况下更长的一段时间)。

看起来您正在尝试使用 HostedService 执行额外的启动项,而不是仅仅尝试 运行 将持续应用程序整个生命周期的 task/operation。

如果是这种情况,您可以进行非常简单的设置。您想要从 StartAsync() 方法中 return 的东西是一个任务。当您 return a Task.CompletedTask 时,您是在说工作已经完成并且没有代码执行 - 任务已完成。您想要 return 的代码是在 Task 对象中执行额外启动项的 运行ning。 asp.net 中的 HostedService 的好处在于,任务 运行 持续多长时间并不重要(因为它意味着 运行 应用程序整个生命周期的任务)。

代码示例之前的一个重要说明 - 如果您在任务中使用 Scoped 服务,那么您需要使用 IServiceScopeFactory 生成一个范围,阅读相关内容

如果您将服务方法重构为 return 一项任务,您可以 return 这样:

public Task StartAsync(CancellationToken)
{
    IServiceScope scope = Provider.CreateScope();
    IUtilityService service = scope.ServiceProvider
      .GetRequiredService<IUtilityService>();

    // If Seed returns a Task
    return service.Seed();
}

如果您有 多个 服务方法,所有 return 一个任务,您可以 return 一个正在等待所有任务完成的任务

public Task StartAsync(CancellationToken)
{
    IServiceScope scope = Provider.CreateScope();
    IUtilityService service = scope.ServiceProvider
      .GetRequiredService<IUtilityService>();
    ISomeOtherService someOtherService = scope.ServiceProvider
      .GetRequiredService<ISomeOtherService>();

    var tasks = new List<Task>();

    tasks.Add(service.Seed());
    tasks.Add(someOtherService.SomeOtherStartupTask());

    return Task.WhenAll(tasks);
}

如果您的启动任务做了很多 CPU 绑定工作,只需 return a Task.Run(() => {});

public Task StartAsync(CancellationToken)
{
    // Return a task which represents my long running cpu startup work...
    return Task.Run(() => {

        IServiceScope scope = Provider.CreateScope();
        IUtilityService service = scope.ServiceProvider
          .GetRequiredService<IUtilityService>();

        service.LongRunningCpuStartupMethod1();
        service.LongRunningCpuStartupMethod2();
    }
}

要使用您的取消令牌,下面的一些示例代码展示了如何完成,方法是在 Try/Catch 中捕获 TaskCanceledException,然后强制退出我们的 运行ning 循环。

然后我们继续执行 运行 整个应用程序生命周期的任务。 这是我用于所有 HostedService 实现的基础 class,这些实现旨在在应用程序关闭之前永不停止 运行ning。

public abstract class HostedService : IHostedService
{
    // Example untested base class code kindly provided by David Fowler: https://gist.github.com/davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3

    private Task _executingTask;
    private CancellationTokenSource _cts;

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Create a linked token so we can trigger cancellation outside of this token's cancellation
        _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

        // Store the task we're executing
        _executingTask = ExecuteAsync(_cts.Token);

        // If the task is completed then return it, otherwise it's running
        return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        // Stop called without start
        if (_executingTask == null)
        {
            return;
        }

        // Signal cancellation to the executing method
        _cts.Cancel();

        // Wait until the task completes or the stop token triggers
        await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));

        // Throw if cancellation triggered
        cancellationToken.ThrowIfCancellationRequested();
    }

    // Derived classes should override this and execute a long running method until 
    // cancellation is requested
    protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}

在此 Base Class 中,您将看到调用 StartAsync 时,我们会调用我们的 ExecuteAsync() 方法,该方法 return 是一个包含 while 循环的任务 - 任务不会停止 运行ning 直到我们的取消令牌被触发,或者应用程序 gracefully/forcefully 停止。

ExecuteAsync() 方法需要由继承自该基础 class 的任何 class 实现,这应该是您的所有 HostedService 的。

这是一个 HostedService 实现示例,它继承自此 Base class,旨在每 30 秒签入一次。您会注意到 ExecuteAsync() 方法进入 while 循环并且永远不会退出 - 它每秒 'tick' 一次,这是您可以调用其他方法的地方,例如定期检查另一台服务器。此循环中的所有代码都在任务中 returned 到 StartAsync() 并 returned 到调用方。直到 while 循环退出或应用程序终止,或取消令牌被触发,任务才会终止。

public class CounterHostedService : HostedService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILog _logger;

    public CounterHostedService(IServiceScopeFactory scopeFactory, ILog logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    // Checkin every 30 seconds
    private int CheckinFrequency = 30;
    private DateTime CheckedIn;

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        int counter = 0;

        var runningTasks = new List<Task>();
        while (true)
        {
            // This loop will run for the lifetime of the application.

            // Time since last checkin is checked every tick. If time since last exceeds the frequency, we perform the action without breaking the execution of our main Task
            var timeSinceCheckin = (DateTime.UtcNow - CheckedIn).TotalSeconds;
            if (timeSinceCheckin > CheckinFrequency)
            {
                var checkinTask = Checkin();
                runningTasks.Add(checkinTask);
            }

            try
            {
                // The loop will 'tick' every second.
                await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
            }
            catch (TaskCanceledException)
            {
                // Break out of the long running task because the Task was cancelled externally
                break;
            }

            counter++;
        }
    }

    // Custom override of StopAsync. This is only triggered when the application
    // GRACEFULLY shuts down. If it is not graceful, this code will not execute. Neither will the code for StopAsync in the base method.
    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.Info($"HostedService Gracefully Shutting down");

        // Perform base StopAsync
        await base.StopAsync(cancellationToken);
    }

    // Creates a task that performs a checkin, and returns the running task
    private Task Checkin()
    {
        return Task.Run(async () =>
        {
            // await DoTheThingThatWillCheckin();
        });
    }
}

请注意,您还可以重写 StopAsync() 方法来执行一些日志记录以及关闭事件所需的任何其他操作。尽量避免 StopAsync 中的关键逻辑,因为它不能保证被调用。