BackgroundService Graceful Shutdown - 完成工作并写入数据库

BackgroundService Graceful Shutdown - Complete work and write to DB

我有一个后台工作者实现了 BackgroundService(由 MS 提供)。

查看这个简单的实现:

public class MyService : BackgroundService {

    private readonly MyDbContext _context;

    public MyService(MyDbContext context) {
        //...
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try {
            while (true)
            {
                stoppingToken.ThrowIfCancellationRequested();
                // Do some work
            }
        } catch(OperationCancelledException) {
            _context.Add(new MyLogMessage(){ Error = "MyService cancelled!" });
            _context.SaveChanges();
        }
        // ...
    }
}

当请求正常关闭(在控制台中:CTRL+C)时,catch 块被触发,而且 SaveChanges() 似乎也被执行。但是,有时错误会存储到数据库中,而大多数时候不会。此外,EntityFramework 正在控制台上打印一条插入语句,但日志不在数据库中。 我假设关闭发生得比将数据写入数据库更快? 谁能给我提示如何处理这种情况并将错误存储到数据库中?

应用程序关闭时,stoppingToken 似乎没有按预期取消。我设法使用 IHostApplicationLifetime 和一个新字段解决了这个问题,如果正在进行关机,我可以在其中存储。

public class TestService : BackgroundService {
    private readonly IHostApplicationLifetime _lifetime;
    private readonly ILogger<TestService> _logger;

    private bool _shutownRequested;

    public TestService(IHostApplicationLifetime lifetime, ILogger<TestService> logger) {
        _lifetime = lifetime;
        _logger = logger;
    }

    public override Task StartAsync(CancellationToken cancellationToken) {
        _lifetime.ApplicationStopping.Register(OnShutdown);
        return Task.CompletedTask;
    }

    private void OnShutdown() {
        _shutdownRequested = true;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
        try {
            while(true) {
                stoppingToken.ThrowIfCancellationRequested();
                if(_shutdownRequested) {
                    throw new OperationCanceledException();
                }

                await Task.Delay(100, CancellationToken.None);
            }
        } catch(OperationCanceledException) {
            _logger.LogWarning("TestService canceled");
        }
    }
}

现在在那里抛出一个新的异常可能会更好,但作为一个例子它就可以了。

日志条目未出现在数据库中的原因是主机关闭时间低于在 while 循环中处理任务并将日志发送到数据库所需的时间。默认超时为 5 秒。

您可以做的是将超时增加到一个更大的值,例如一分钟和两分钟:

services.Configure<HostOptions>(
    opts => opts.ShutdownTimeout = TimeSpan.FromMinutes(2));

确保为服务留出足够的时间来完成 while 循环内的迭代并记录消息。

请查看Extending the shutdown timeout setting to ensure graceful IHostedService shutdown了解更多详情。