Tengo un Trabajador de fondo que implementa el Servicio de fondo (proporcionado por MS).
Vea esta implementación simple:
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(); } // ... } } Cuando se solicita el apagado correcto (en la consola: CTRL+C), se activa el bloque catch y también parece que se ejecuta SaveChanges() . Pero, a veces, el error se almacena en la base de datos y la mayoría de las veces no. Además, EntityFramework está imprimiendo una instrucción de inserción en la consola, pero el registro no está en la base de datos. ¿Supongo que el apagado está ocurriendo más rápido que escribir los datos en la base de datos? ¿Alguien puede darme una pista sobre cómo manejar esta situación y almacenar el error en la base de datos?
La razón por la que la entrada de registro no aparece en la base de datos es que el período de apagado del host es inferior al que se necesita para procesar una tarea en un ciclo while y enviar un registro a la base de datos. El tiempo de espera predeterminado es de 5 segundos.
Lo que podría hacer es aumentar el tiempo de espera a un valor mayor, por ejemplo, un minuto a dos:
services.Configure<HostOptions>( opts => opts.ShutdownTimeout = TimeSpan.FromMinutes(2)); Asegúrese de dejar suficiente tiempo para que un servicio termine la iteración dentro de un ciclo while y registre el mensaje.
Consulte Ampliación de la configuración del tiempo de espera de apagado para garantizar un apagado correcto de IHostedService para obtener más detalles.
Parece que el token de stoppingToken no se cancela como se esperaba cuando la aplicación se cierra. Logré solucionar esto usando IHostApplicationLifetime y un nuevo campo donde puedo almacenar si hay un apagado en curso.
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"); } } }Ahora podría ser mejor lanzar una nueva excepción allí, pero como ejemplo servirá.