Tareas en segundo plano con servicios hospedados en ASP.NET CoreBackground tasks with hosted services in ASP.NET Core

Por Luke LathamBy Luke Latham

En ASP.NET Core, las tareas en segundo plano se pueden implementar como servicios hospedados.In ASP.NET Core, background tasks can be implemented as hosted services. Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. En este tema se incluyen tres ejemplos de servicio hospedado:This topic provides three hosted service examples:

Vea o descargue el código de ejemplo (cómo descargarlo)View or download sample code (how to download)

La aplicación de ejemplo se ofrece en dos versiones:The sample app is provided in two versions:

  • Host web: el host web resulta útil para hospedar aplicaciones web.Web Host – Web Host is useful for hosting web apps. El código de ejemplo que se muestra en este tema corresponde a la versión de host web del ejemplo.The example code shown in this topic is from Web Host version of the sample. Para más información, vea el sitio web Host de web.For more information, see the Web Host topic.
  • Host genérico: el host genérico es nuevo en ASP.NET Core 2.1.Generic Host – Generic Host is new in ASP.NET Core 2.1. Para más información, vea el sitio web Host genérico.For more information, see the Generic Host topic.

Plantilla Worker ServiceWorker Service template

La plantilla Worker Service de ASP.NET Core sirve de punto de partida para escribir aplicaciones de servicio de larga duración.The ASP.NET Core Worker Service template provides a starting point for writing long running service apps. Para usar la plantilla como base de una aplicación de servicios hospedados:To use the template as a basis for a hosted services app:

  1. Cree un nuevo proyecto.Create a new project.
  2. Seleccione Worker Service (Servicio de Worker).Select Worker Service. Seleccione Siguiente.Select Next.
  3. Proporcione un nombre para el proyecto en el campo Nombre del proyecto o acepte el predeterminado.Provide a project name in the Project name field or accept the default project name. Seleccione Crear.Select Create.
  4. En el cuadro de diálogo Crear un servicio de Worker, seleccione Crear.In the Create a new Worker service dialog, select Create.

PackagePackage

Se agrega implícitamente una referencia de paquete al paquete Microsoft.Extensions.Hosting para las aplicaciones ASP.NET Core.A package reference to the Microsoft.Extensions.Hosting package is added implicitly for ASP.NET Core apps.

Interfaz IHostedServiceIHostedService interface

La interfaz IHostedService define dos métodos para los objetos administrados por el host:The IHostedService interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken): StartAsync contiene la lógica para iniciar la tarea en segundo plano.StartAsync(CancellationToken)StartAsync contains the logic to start the background task. Se llama a StartAsync antes de que:StartAsync is called before:

    El comportamiento predeterminado se puede cambiar para que el StartAsync del servicio hospedado se ejecute después de que se haya configurado la canalización de la aplicación y se haya llamado a ApplicationStarted.The default behavior can be changed so that the hosted service's StartAsync runs after the app's pipeline has been configured and ApplicationStarted is called. Para cambiar el comportamiento predeterminado, agregue el servicio hospedado (VideosWatcher en el ejemplo siguiente) después de llamar a ConfigureWebHostDefaults:To change the default behavior, add the hosted service (VideosWatcher in the following example) after calling ConfigureWebHostDefaults:

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
    
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .ConfigureServices(services =>
                {
                    services.AddHostedService<VideosWatcher>();
                });
    }
    
  • StopAsync(CancellationToken): se activa cuando el host está realizando un cierre estable.StopAsync(CancellationToken) – Triggered when the host is performing a graceful shutdown. StopAsync contiene la lógica para finalizar la tarea en segundo plano.StopAsync contains the logic to end the background task. Implemente IDisposable y los finalizadores (destructores) para desechar los recursos no administrados.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    El token de cancelación tiene un tiempo de espera predeterminado de cinco segundos para indicar que el proceso de cierre ya no debería ser estable.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Cuando se solicita la cancelación en el token:When cancellation is requested on the token:

    • Se deben anular las operaciones restantes en segundo plano que realiza la aplicación.Any remaining background operations that the app is performing should be aborted.
    • Los métodos llamados en StopAsync deberían devolver contenido al momento.Any methods called in StopAsync should return promptly.

    No obstante, las tareas no se abandonan después de solicitar la cancelación, sino que el autor de la llamada espera a que se completen todas las tareas.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Si la aplicación se cierra inesperadamente (por ejemplo, porque se produzca un error en el proceso de la aplicación), puede que no sea posible llamar a StopAsync.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Por lo tanto, los métodos llamados o las operaciones llevadas a cabo en StopAsync podrían no producirse.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Para ampliar el tiempo de espera predeterminado de apagado de 5 segundos, establezca:To extend the default five second shutdown timeout, set:

El servicio hospedado se activa una vez al inicio de la aplicación y se cierra de manera estable cuando dicha aplicación se cierra.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Si se produce un error durante la ejecución de una tarea en segundo plano, hay que llamar a Dispose, aun cuando no se haya llamado a StopAsync.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

BackgroundServiceBackgroundService

BackgroundService es una clase base para implementar un IHostedService de larga duración.BackgroundService is a base class for implementing a long running IHostedService. BackgroundService proporciona el método abstracto ExecuteAsync(CancellationToken stoppingToken) para contener la lógica del servicio.BackgroundService provides the ExecuteAsync(CancellationToken stoppingToken) abstract method to contain the service's logic. stoppingToken se desencadena cuando se realiza una llamada a IHostedService.StopAsync.The stoppingToken is triggered when IHostedService.StopAsync is called. La implementación de este método devuelve Task, que representa toda la duración del servicio en segundo plano.The implementation of this method returns a Task that represents the entire lifetime of the background service.

Además, también puede invalidar los métodos definidos en IHostedService para ejecutar el código de inicio y de apagado para el servicio:In addition, optionally override the methods defined on IHostedService to run startup and shutdown code for your service:

  • Se realiza una llamada a StopAsync(CancellationToken cancellationToken)StopAsync cuando el host de la aplicación está realizando un apagado estable.StopAsync(CancellationToken cancellationToken)StopAsync is called when the application host is performing a graceful shutdown. Se envía una señal a cancellationToken cuando el host decide finalizar forzosamente el servicio.The cancellationToken is signalled when the host decides to forcibly terminate the service. Si se invalida este método, debe realizar una llamada (y await) al método de la clase base para asegurarse de que el servicio se apaga correctamente.If this method is overridden, you must call (and await) the base class method to ensure the service shuts down properly.
  • Se realiza una llamada a StartAsync(CancellationToken cancellationToken)StartAsync para iniciar el servicio en segundo plano.StartAsync(CancellationToken cancellationToken)StartAsync is called to start the background service. Se envía una señal a cancellationToken si se interrumpe el proceso de inicio.The cancellationToken is signalled if the startup process is interrupted. La implementación devuelve Task, que representa el proceso de inicio del servicio.The implementation returns a Task that represents the startup process for the service. No se inicia ningún otro servicio hasta que se complete Task.No further services are started until this Task completes. Si se invalida este método, debe realizar una llamada (y await) al método de la clase base para asegurarse de que el servicio se inicia correctamente.If this method is overridden, you must call (and await) the base class method to ensure the service starts properly.

Tareas en segundo plano temporizadasTimed background tasks

Una tarea en segundo plano temporizada hace uso de la clase System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. El temporizador activa el método DoWork de la tarea.The timer triggers the task's DoWork method. El temporizador está deshabilitado en StopAsync y se desecha cuando el contenedor de servicios se elimina en Dispose:The timer is disabled on StopAsync and disposed when the service container is disposed on Dispose:

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        executionCount++;

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}"), executionCount;
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

El servicio se registra en IHostBuilder.ConfigureServices (Program.cs) con el método de extensión AddHostedService:The service is registered in IHostBuilder.ConfigureServices (Program.cs) with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Consumir un servicio con ámbito en una tarea en segundo planoConsuming a scoped service in a background task

Para usar servicios con ámbito en un elemento BackgroundService, cree un ámbito.To use scoped services within a BackgroundService, create a scope. No se crean ámbitos de forma predeterminada para los servicios hospedados.No scope is created for a hosted service by default.

El servicio de tareas en segundo plano con ámbito contiene la lógica de la tarea en segundo plano.The scoped background task service contains the background task's logic. En el ejemplo siguiente:In the following example:

  • El servicio es asincrónico.The service is asynchronous. El método DoWork devuelve un objeto Task.The DoWork method returns a Task. Para fines de demostración, se espera un retraso de diez segundos en el método DoWork.For demonstration purposes, a delay of ten seconds is awaited in the DoWork method.
  • ILogger se inserta en el servicio.An ILogger is injected into the service.
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public async Task DoWork(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            executionCount++;

            _logger.LogInformation(
                "Scoped Processing Service is working. Count: {Count}", executionCount);

            await Task.Delay(10000, stoppingToken);
        }
    }
}

El servicio hospedado crea un ámbito con el fin de resolver el servicio de tareas en segundo plano con ámbito para llamar a su método DoWork.The hosted service creates a scope to resolve the scoped background task service to call its DoWork method. DoWork devuelve Task, que se espera en ExecuteAsync:DoWork returns a Task, which is awaited in ExecuteAsync:

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        await DoWork(stoppingToken);
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWork(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await Task.CompletedTask;
    }
}

Los servicios se registran en IHostBuilder.ConfigureServices (Program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). El servicio hospedado se registra en con el método de extensión AddHostedService:The hosted service is registered with the AddHostedService extension method:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

Tareas en segundo plano en colaQueued background tasks

La cola de tareas en segundo plano se basa en QueueBackgroundWorkItem de .NET 4.x (está previsto que se integre en ASP.NET Core):A background task queue is based on the .NET 4.x QueueBackgroundWorkItem (tentatively scheduled to be built-in for ASP.NET Core):

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

    Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = 
        new ConcurrentQueue<Func<CancellationToken, Task>>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(
        Func<CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);

        return workItem;
    }
}

En el ejemplo QueueHostedService siguiente:In the following QueueHostedService example:

  • El método BackgroundProcessing devuelve Task, que se espera en ExecuteAsync.The BackgroundProcessing method returns a Task, which is awaited in ExecuteAsync.
  • Las tareas en segundo plano que están en cola se quitan de ella y se ejecutan en BackgroundProcessing.Background tasks in the queue are dequeued and executed in BackgroundProcessing.
public class QueuedHostedService : BackgroundService
{
    private CancellationTokenSource _shutdown = 
        new CancellationTokenSource();
    private Task _backgroundTask;
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILogger<QueuedHostedService> logger)
    {
        TaskQueue = taskQueue;
        _logger = logger;
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

        _backgroundTask = Task.Run(async () =>
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    await BackgroundProcessing();
                }
            }, stoppingToken);

        await _backgroundTask;
    }

    private async Task BackgroundProcessing()
    {
        while (!_shutdown.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(_shutdown.Token);

            try
            {
                await workItem(_shutdown.Token);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queued Hosted Service is stopping.");

        _shutdown.Cancel();

        await Task.WhenAny(_backgroundTask, 
                Task.Delay(Timeout.Infinite, stoppingToken));
    }
}

Un servicio MonitorLoop controla las tareas de puesta en cola para el servicio hospedado cada vez que se selecciona la tecla w en un dispositivo de entrada:A MonitorLoop service handles enqueuing tasks for the hosted service whenever the w key is selected on an input device:

  • IBackgroundTaskQueue se inserta en el servicio MonitorLoop.The IBackgroundTaskQueue is injected into the MonitorLoop service.
  • Se llama a IBackgroundTaskQueue.QueueBackgroundWorkItem para poner en cola el elemento de trabajo.IBackgroundTaskQueue.QueueBackgroundWorkItem is called to enqueue a work item.
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue, 
        ILogger<MonitorLoop> logger, 
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("Monitor Loop is starting.");

        // Run a console user input loop in a background thread
        Task.Run(() => Monitor());
    }

    public void Monitor()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                _taskQueue.QueueBackgroundWorkItem(async token =>
                {
                    var guid = Guid.NewGuid().ToString();

                    for (int delayLoop = 0; delayLoop < 3; delayLoop++)
                    {
                        _logger.LogInformation(
                            "Queued Background Task {Guid} is running. {DelayLoop}/3", guid, delayLoop);
                        await Task.Delay(TimeSpan.FromSeconds(5), token);
                    }

                    _logger.LogInformation(
                        "Queued Background Task {Guid} is complete. 3/3", guid);
                });
            }
        }
    }
}

Los servicios se registran en IHostBuilder.ConfigureServices (Program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). El servicio hospedado se registra en con el método de extensión AddHostedService:The hosted service is registered with the AddHostedService extension method:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

En ASP.NET Core, las tareas en segundo plano se pueden implementar como servicios hospedados.In ASP.NET Core, background tasks can be implemented as hosted services. Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. En este tema se incluyen tres ejemplos de servicio hospedado:This topic provides three hosted service examples:

Vea o descargue el código de ejemplo (cómo descargarlo)View or download sample code (how to download)

La aplicación de ejemplo se ofrece en dos versiones:The sample app is provided in two versions:

  • Host web: el host web resulta útil para hospedar aplicaciones web.Web Host – Web Host is useful for hosting web apps. El código de ejemplo que se muestra en este tema corresponde a la versión de host web del ejemplo.The example code shown in this topic is from Web Host version of the sample. Para más información, vea el sitio web Host de web.For more information, see the Web Host topic.
  • Host genérico: el host genérico es nuevo en ASP.NET Core 2.1.Generic Host – Generic Host is new in ASP.NET Core 2.1. Para más información, vea el sitio web Host genérico.For more information, see the Generic Host topic.

PackagePackage

Haga referencia al metapaquete Microsoft.AspNetCore.App o agregue una referencia de paquete al paquete Microsoft.Extensions.Hosting.Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Hosting package.

Interfaz IHostedServiceIHostedService interface

Los servicios hospedados implementan la interfaz IHostedService.Hosted services implement the IHostedService interface. Esta interfaz define dos métodos para los objetos administrados por el host:The interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken): StartAsync contiene la lógica para iniciar la tarea en segundo plano.StartAsync(CancellationToken)StartAsync contains the logic to start the background task. Al utilizar el host web, se llama a StartAsync después de que el servidor se haya iniciado y se haya activado IApplicationLifetime.ApplicationStarted.When using the Web Host, StartAsync is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered. Al utilizar el host genérico, se llama a StartAsync antes de que se desencadene ApplicationStarted.When using the Generic Host, StartAsync is called before ApplicationStarted is triggered.

  • StopAsync(CancellationToken): se activa cuando el host está realizando un cierre estable.StopAsync(CancellationToken) – Triggered when the host is performing a graceful shutdown. StopAsync contiene la lógica para finalizar la tarea en segundo plano.StopAsync contains the logic to end the background task. Implemente IDisposable y los finalizadores (destructores) para desechar los recursos no administrados.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    El token de cancelación tiene un tiempo de espera predeterminado de cinco segundos para indicar que el proceso de cierre ya no debería ser estable.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Cuando se solicita la cancelación en el token:When cancellation is requested on the token:

    • Se deben anular las operaciones restantes en segundo plano que realiza la aplicación.Any remaining background operations that the app is performing should be aborted.
    • Los métodos llamados en StopAsync deberían devolver contenido al momento.Any methods called in StopAsync should return promptly.

    No obstante, las tareas no se abandonan después de solicitar la cancelación, sino que el autor de la llamada espera a que se completen todas las tareas.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Si la aplicación se cierra inesperadamente (por ejemplo, porque se produzca un error en el proceso de la aplicación), puede que no sea posible llamar a StopAsync.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Por lo tanto, los métodos llamados o las operaciones llevadas a cabo en StopAsync podrían no producirse.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Para ampliar el tiempo de espera predeterminado de apagado de 5 segundos, establezca:To extend the default five second shutdown timeout, set:

El servicio hospedado se activa una vez al inicio de la aplicación y se cierra de manera estable cuando dicha aplicación se cierra.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Si se produce un error durante la ejecución de una tarea en segundo plano, hay que llamar a Dispose, aun cuando no se haya llamado a StopAsync.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Tareas en segundo plano temporizadasTimed background tasks

Una tarea en segundo plano temporizada hace uso de la clase System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. El temporizador activa el método DoWork de la tarea.The timer triggers the task's DoWork method. El temporizador está deshabilitado en StopAsync y se desecha cuando el contenedor de servicios se elimina en Dispose:The timer is disabled on StopAsync and disposed when the service container is disposed on Dispose:

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

El servicio se registra en Startup.ConfigureServices con el método de extensión AddHostedService:The service is registered in Startup.ConfigureServices with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Consumir un servicio con ámbito en una tarea en segundo planoConsuming a scoped service in a background task

Para usar servicios con ámbito en un elemento IHostedService, cree un ámbito.To use scoped services within an IHostedService, create a scope. No se crean ámbitos de forma predeterminada para los servicios hospedados.No scope is created for a hosted service by default.

El servicio de tareas en segundo plano con ámbito contiene la lógica de la tarea en segundo plano.The scoped background task service contains the background task's logic. En el siguiente ejemplo, ILogger se inserta en el servicio:In the following example, an ILogger is injected into the service:

internal interface IScopedProcessingService
{
    void DoWork();
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public void DoWork()
    {
        _logger.LogInformation("Scoped Processing Service is working.");
    }
}

El servicio hospedado crea un ámbito con objeto de resolver el servicio de tareas en segundo plano con ámbito para llamar a su método DoWork:The hosted service creates a scope to resolve the scoped background task service to call its DoWork method:

internal class ConsumeScopedServiceHostedService : IHostedService
{
    private readonly ILogger _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is starting.");

        DoWork();

        return Task.CompletedTask;
    }

    private void DoWork()
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            scopedProcessingService.DoWork();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        return Task.CompletedTask;
    }
}

Los servicios se registran en Startup.ConfigureServices.The services are registered in Startup.ConfigureServices. La implementación IHostedService se registra con el método de extensión AddHostedService:The IHostedService implementation is registered with the AddHostedService extension method:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

Tareas en segundo plano en colaQueued background tasks

La cola de tareas en segundo plano se basa en QueueBackgroundWorkItem de .NET 4.x (está previsto que se integre en ASP.NET Core):A background task queue is based on the .NET 4.x QueueBackgroundWorkItem (tentatively scheduled to be built-in for ASP.NET Core):

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

    Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = 
        new ConcurrentQueue<Func<CancellationToken, Task>>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(
        Func<CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);

        return workItem;
    }
}

En QueueHostedService, las tareas en segundo plano en la cola se quitan de la cola y se ejecutan como un servicio BackgroundService, que es una clase base para implementar IHostedService de ejecución prolongada:In QueueHostedService, background tasks in the queue are dequeued and executed as a BackgroundService, which is a base class for implementing a long running IHostedService:

public class QueuedHostedService : BackgroundService
{
    private readonly ILogger _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILoggerFactory loggerFactory)
    {
        TaskQueue = taskQueue;
        _logger = loggerFactory.CreateLogger<QueuedHostedService>();
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected async override Task ExecuteAsync(
        CancellationToken cancellationToken)
    {
        _logger.LogInformation("Queued Hosted Service is starting.");

        while (!cancellationToken.IsCancellationRequested)
        {
            var workItem = await TaskQueue.DequeueAsync(cancellationToken);

            try
            {
                await workItem(cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                   "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }

        _logger.LogInformation("Queued Hosted Service is stopping.");
    }
}

Los servicios se registran en Startup.ConfigureServices.The services are registered in Startup.ConfigureServices. La implementación IHostedService se registra con el método de extensión AddHostedService:The IHostedService implementation is registered with the AddHostedService extension method:

services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

En la clase de modelo de página de índice:In the Index page model class:

  • Se inserta IBackgroundTaskQueue en el constructor y se asigna a Queue.The IBackgroundTaskQueue is injected into the constructor and assigned to Queue.
  • Se inserta IServiceScopeFactory y se asigna a _serviceScopeFactory.An IServiceScopeFactory is injected and assigned to _serviceScopeFactory. Se usa el generador se para crear instancias de IServiceScope, que se usa para crear servicios dentro de un ámbito.The factory is used to create instances of IServiceScope, which is used to create services within a scope. Se crea un ámbito para poder usar el elemento AppDbContext de la aplicación (un servicio con ámbito) para escribir registros de base de datos en IBackgroundTaskQueue (un servicio de singleton).A scope is created in order to use the app's AppDbContext (a scoped service) to write database records in the IBackgroundTaskQueue (a singleton service).
public class IndexModel : PageModel
{
    private readonly AppDbContext _db;
    private readonly ILogger _logger;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public IndexModel(AppDbContext db, IBackgroundTaskQueue queue, 
        ILogger<IndexModel> logger, IServiceScopeFactory serviceScopeFactory)
    {
        _db = db;
        _logger = logger;
        Queue = queue;
        _serviceScopeFactory = serviceScopeFactory;
    }

    public IBackgroundTaskQueue Queue { get; }

Cuando se hace clic en el botón Agregar tarea en la página de índice, se ejecuta el método OnPostAddTask.When the Add Task button is selected on the Index page, the OnPostAddTask method is executed. Se llama a QueueBackgroundWorkItem para poner en cola un elemento de trabajo:QueueBackgroundWorkItem is called to enqueue a work item:

public IActionResult OnPostAddTaskAsync()
{
    Queue.QueueBackgroundWorkItem(async token =>
    {
        var guid = Guid.NewGuid().ToString();

        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var scopedServices = scope.ServiceProvider;
            var db = scopedServices.GetRequiredService<AppDbContext>();

            for (int delayLoop = 1; delayLoop < 4; delayLoop++)
            {
                try
                {
                    db.Messages.Add(
                        new Message() 
                        { 
                            Text = $"Queued Background Task {guid} has " +
                                $"written a step. {delayLoop}/3"
                        });
                    await db.SaveChangesAsync();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, 
                        "An error occurred writing to the " +
                        "database. Error: {Message}", ex.Message);
                }

                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
        }

        _logger.LogInformation(
            "Queued Background Task {Guid} is complete. 3/3", guid);
    });

    return RedirectToPage();
}

Recursos adicionalesAdditional resources