【问题标题】:Executing async code after startup, once API is up for requests启动后执行异步代码,一旦 API 用于请求
【发布时间】:2021-12-10 10:36:37
【问题描述】:

我正在尝试在我的 Web API 启动时执行逻辑,并且此代码也将是异步的。

我想到了IStartupFilter,但这是同步,这是个问题。 StartUp.Configure() 也不是一个选项,同步。

我想到了IHostedService,但它在应用程序完成加载和请求之前运行。

还有其他方法吗?

【问题讨论】:

  • 您想要执行的内容的性质是什么?它是你需要运行一次的东西,一个长期存在的过程,还是......?
  • 每次 api 启动时。运行时间不是特别长。
  • 一个选项是andrewlock.net/…

标签: c# asp.net-core asp.net-core-5.0


【解决方案1】:

您可能没有意识到运行应用程序有 2 个部分,当您调用 IHost.Run/IHost.RunAsync 时默认隐藏这些部分。

基本上是这样的:

var host = CreateHostBuilder(args).Build();
await host.RunAsync();

相当于这个:

var host = CreateHostBuilder(args).Build();

await host.StartAsync();
await host.WaitForShutdownAsync();

当StartAsync 返回时,应用程序已经启动并准备好使用请求,因此您可能想要这样做:

var host = CreateHostBuilder(args).Build();

await host.StartAsync();

await PerformSomeWorkAfterStartupAsync();

await host.WaitForShutdownAsync();

【讨论】:

  • 非常有用。有没有办法注册服务/中间件/等,所以它可以在PerformSomeWorkAfterStartupAsync() 没有开发人员的情况下运行。编辑那个特定的部分?
  • @FrancisDucharme 我不知道,这是经过相当广泛的搜索后我能找到的最好的,因为几个月前我需要类似的东西
【解决方案2】:

实现IStartupTask,在DI中注册StartupTaskRunner和YourStartupTask:

services    
    .AddStartupTasksRunner()
    .AddStartupTask<YourStartupTask>();

这里的代码基于Andrew's Lock 帖子:

public interface IAsyncStartupTask
{
    Task OnStartupAsync(CancellationToken cancellationToken);
}
internal class StartupTasksRunner : IHostedService
{
    private readonly IEnumerable<IAsyncStartupTask> _startupTasks;

    private readonly IHostApplicationLifetime _applicationLifetime;

    private readonly SemaphoreSlim _semaphore;

    public StartupTasksRunner(IEnumerable<IAsyncStartupTask> startupTasks, IHostApplicationLifetime applicationLifetime)
    {
        _startupTasks = startupTasks;
        _applicationLifetime = applicationLifetime;

        _semaphore = new SemaphoreSlim(0);
        applicationLifetime.ApplicationStarted.Register(() => _semaphore.Release());
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // wait for ApplicationStarted event to execute when app is listening to web requests
        // await _semaphore.WaitAsync(cancellationToken);

        foreach (var task in _startupTasks)
        {
            try
            {
                await task.OnStartupAsync(cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                // stop the application if failing startup task if fatal
                //_applicationLifetime.StopApplication();
                throw;
            }
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddStartupTasksRunner(this IServiceCollection services)
    {
        return services.AddHostedService<StartupTasksRunner>();
    }

    public static IServiceCollection AddStartupTask<TStartupTask>(this IServiceCollection services)
        where TStartupTask : class, IAsyncStartupTask
    {
        return services.AddSingleton<IAsyncStartupTask, TStartupTask>();
    }
}

备注
应用startup order是:

  1. 开始HostedService的
  2. 启动 Kesterl 服务器
  3. 触发ApplicationStarted事件

如果您想在HostedServices 开始之前运行您的启动任务,您需要确保StartupTasksRunner 在任何其他HostedService 之前注册(它们按照注册顺序启动)。 p>

如果您希望在所有HostedServices 启动后但在应用程序开始接收网络请求之前运行您的启动任务,请确保您在任何一个之后注册StartupTasksRunner其他HostedService。

如果您想在 Kestrel 服务器启动后运行您的启动任务,请取消注释 // await _semaphore.WaitAsync(cancellationToken); 行。请注意,它可能与 Web 请求处理同时运行。

还要注意IHostedService.StartAsync 方法抛出的异常将被框架swallowed。因此,如果您的应用程序需要成功执行启动任务,请取消注释 // _applicationLifetime.StopApplication();

【讨论】:

  • 这是在应用程序准备好之前执行的
  • @FrancisDucharme 查看我的更新答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-06
  • 2017-09-25
相关资源
最近更新 更多