Tarefas em segundo plano com serviços hospedados no ASP.NET CoreBackground tasks with hosted services in ASP.NET Core

Por Jeow li HuanBy Jeow Li Huan

No ASP.NET Core, as tarefas em segundo plano podem ser implementadas como serviços hospedados.In ASP.NET Core, background tasks can be implemented as hosted services. Um serviço hospedado é uma classe com lógica de tarefa em segundo plano que implementa a interface IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. Este tópico fornece três exemplos de serviço hospedado:This topic provides three hosted service examples:

Exibir ou baixar código de exemplo (como baixar)View or download sample code (how to download)

Modelo de serviço de trabalhoWorker Service template

O modelo de Serviço de Trabalho do ASP.NET Core fornece um ponto inicial para escrever aplicativos de serviço de execução prolongada.The ASP.NET Core Worker Service template provides a starting point for writing long running service apps. Um aplicativo criado a partir do modelo de serviço de trabalho especifica o SDK do trabalhador em seu arquivo de projeto:An app created from the Worker Service template specifies the Worker SDK in its project file:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Para usar o modelo como base para um aplicativo de serviços hospedados:To use the template as a basis for a hosted services app:

  1. Criar um novo projeto.Create a new project.
  2. Selecione serviço de trabalho.Select Worker Service. Selecione Avançar.Select Next.
  3. Forneça um nome ao projeto no campo Nome do projeto ou aceite o nome do projeto padrão.Provide a project name in the Project name field or accept the default project name. Selecione Criar.Select Create.
  4. Na caixa de diálogo criar um novo serviço de trabalho , selecione criar.In the Create a new Worker service dialog, select Create.

PacotePackage

Um aplicativo baseado no modelo de serviço de trabalho usa o Microsoft.NET.Sdk.Worker SDK e tem uma referência de pacote explícita para o pacote Microsoft. Extensions. Hosting .An app based on the Worker Service template uses the Microsoft.NET.Sdk.Worker SDK and has an explicit package reference to the Microsoft.Extensions.Hosting package. Por exemplo, consulte o arquivo de projeto do aplicativo de exemplo (BackgroundTasksSample. csproj).For example, see the sample app's project file (BackgroundTasksSample.csproj).

Para aplicativos Web que usam o Microsoft.NET.Sdk.Web SDK, o pacote Microsoft. Extensions. Hosting é referenciado implicitamente da estrutura compartilhada.For web apps that use the Microsoft.NET.Sdk.Web SDK, the Microsoft.Extensions.Hosting package is referenced implicitly from the shared framework. Uma referência de pacote explícita no arquivo de projeto do aplicativo não é necessária.An explicit package reference in the app's project file isn't required.

Interface IHostedServiceIHostedService interface

A IHostedService interface define dois métodos para objetos que são gerenciados pelo host:The IHostedService interface defines two methods for objects that are managed by the host:

  • StartAsync (CancellationToken): StartAsync contém a lógica para iniciar a tarefa em segundo plano.StartAsync(CancellationToken): StartAsync contains the logic to start the background task. StartAsyncé chamado antesde:StartAsync is called before:

    O comportamento padrão pode ser alterado para que o serviço hospedado StartAsync seja executado depois que o pipeline do aplicativo tiver sido configurado e ApplicationStarted chamado.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 alterar o comportamento padrão, adicione o serviço hospedado ( VideosWatcher no exemplo a seguir) após chamar 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): disparado quando o host está executando um desligamento normal.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync contém a lógica para encerrar a tarefa em segundo plano.StopAsync contains the logic to end the background task. Implemente o IDisposable e os finalizadores (destruidores) para descartar todos os recursos não gerenciados.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    O token de cancelamento tem um tempo limite padrão de cinco segundos para indicar que o processo de desligamento não deve mais ser normal.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Quando for solicitado um cancelamento no token:When cancellation is requested on the token:

    • Todas as demais operações em segundo plano que o aplicativo estiver executando deverão ser anuladas.Any remaining background operations that the app is performing should be aborted.
    • Todos os métodos chamados em StopAsync deverão retornar imediatamente.Any methods called in StopAsync should return promptly.

    No entanto, as tarefas não são abandonadas após a solicitação de cancelamento—o chamador aguarda a conclusão de todas as tarefas.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Se o aplicativo for desligado inesperadamente (por exemplo, em uma falha do processo do aplicativo), StopAsync não poderá ser chamado.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Portanto, os métodos chamados ou operações realizadas em StopAsync talvez não ocorram.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Para estender o tempo limite de desligamento padrão de cinco segundos, defina:To extend the default five second shutdown timeout, set:

O serviço hospedado é ativado uma única vez na inicialização do aplicativo e desligado normalmente durante o desligamento do aplicativo.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Se um erro for gerado durante a execução da tarefa em segundo plano, Dispose deverá ser chamado mesmo se StopAsync não for chamado.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Classe base BackgroundServiceBackgroundService base class

BackgroundServiceé uma classe base para implementar uma longa execução IHostedService .BackgroundService is a base class for implementing a long running IHostedService.

ExecuteAsync (CancellationToken) é chamado para executar o serviço em segundo plano.ExecuteAsync(CancellationToken) is called to run the background service. A implementação retorna um Task que representa o tempo de vida inteiro do serviço em segundo plano.The implementation returns a Task that represents the entire lifetime of the background service. Nenhum serviço adicional é iniciado até que ExecuteAsync se torne assíncrono, por exemplo, chamando await .No further services are started until ExecuteAsync becomes asynchronous, such as by calling await. Evite executar tempo demorado, bloqueando o trabalho de inicialização no ExecuteAsync .Avoid performing long, blocking initialization work in ExecuteAsync. Os blocos de host em StopAsync (CancellationToken) aguardando ExecuteAsync para serem concluídos.The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync to complete.

O token de cancelamento é disparado quando IHostedService. StopAsync é chamado.The cancellation token is triggered when IHostedService.StopAsync is called. Sua implementação do ExecuteAsync deve ser concluída imediatamente quando o token de cancelamento é acionado para desligar o serviço normalmente.Your implementation of ExecuteAsync should finish promptly when the cancellation token is fired in order to gracefully shut down the service. Caso contrário, o serviço será desligado sem nenhum normal no tempo limite do desligamento.Otherwise, the service ungracefully shuts down at the shutdown timeout. Para obter mais informações, consulte a seção interface IHostedService .For more information, see the IHostedService interface section.

Tarefas em segundo plano temporizadasTimed background tasks

Uma tarefa em segundo plano temporizada usa a classe System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. O temporizador dispara o método DoWork da tarefa.The timer triggers the task's DoWork method. O temporizador é desabilitado em StopAsync e descartado quando o contêiner de serviço é descartado em 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)
    {
        var count = Interlocked.Increment(ref executionCount);

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

    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();
    }
}

O Timer não aguarda as execuções anteriores do DoWork serem concluídas, portanto, a abordagem mostrada pode não ser adequada para cada cenário.The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario. Interlocked. Increment é usado para incrementar o contador de execução como uma operação atômica, o que garante que vários threads não sejam atualizados executionCount simultaneamente.Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update executionCount concurrently.

O serviço está registrado em IHostBuilder.ConfigureServices (Program.cs) com o AddHostedService método de extensão:The service is registered in IHostBuilder.ConfigureServices (Program.cs) with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Consumindo um serviço com escopo em uma tarefa em segundo planoConsuming a scoped service in a background task

Para usar serviços com escopo em um BackgroundService, crie um escopo.To use scoped services within a BackgroundService, create a scope. Por padrão, nenhum escopo é criado para um serviço hospedado.No scope is created for a hosted service by default.

O serviço da tarefa em segundo plano com escopo contém a lógica da tarefa em segundo plano.The scoped background task service contains the background task's logic. No exemplo a seguir:In the following example:

  • O serviço é assíncrono.The service is asynchronous. O método DoWork retorna um Task.The DoWork method returns a Task. Para fins de demonstração, um atraso de dez segundos é aguardado no DoWork método.For demonstration purposes, a delay of ten seconds is awaited in the DoWork method.
  • Um ILogger é injetado no serviço.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);
        }
    }
}

O serviço hospedado cria um escopo para resolver o serviço de tarefa em segundo plano com escopo para chamar seu DoWork método.The hosted service creates a scope to resolve the scoped background task service to call its DoWork method. DoWorkRetorna um Task , que é esperado em 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;
    }
}

Os serviços são registrados em IHostBuilder.ConfigureServices (Program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado está registrado com o AddHostedService método de extensão:The hosted service is registered with the AddHostedService extension method:

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

Tarefas em segundo plano na filaQueued background tasks

Uma fila de tarefas em segundo plano é baseada no .NET 4. x QueueBackgroundWorkItem :A background task queue is based on the .NET 4.x QueueBackgroundWorkItem:

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;
    }
}

No exemplo a seguir QueueHostedService :In the following QueueHostedService example:

  • O BackgroundProcessing método retorna um Task , que é esperado em ExecuteAsync .The BackgroundProcessing method returns a Task, which is awaited in ExecuteAsync.
  • As tarefas em segundo plano na fila são removidas da fila e executadas no BackgroundProcessing .Background tasks in the queue are dequeued and executed in BackgroundProcessing.
  • Os itens de trabalho são aguardados antes de o serviço parar StopAsync .Work items are awaited before the service stops in StopAsync.
public class QueuedHostedService : BackgroundService
{
    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}");

        await BackgroundProcessing(stoppingToken);
    }

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

            try
            {
                await workItem(stoppingToken);
            }
            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.");

        await base.StopAsync(stoppingToken);
    }
}

Um MonitorLoop serviço lida com tarefas de enfileiramento para o serviço hospedado sempre que a w chave é selecionada em um dispositivo de entrada:A MonitorLoop service handles enqueuing tasks for the hosted service whenever the w key is selected on an input device:

  • O IBackgroundTaskQueue é injetado no MonitorLoop serviço.The IBackgroundTaskQueue is injected into the MonitorLoop service.
  • IBackgroundTaskQueue.QueueBackgroundWorkItemé chamado para enfileirar um item de trabalho.IBackgroundTaskQueue.QueueBackgroundWorkItem is called to enqueue a work item.
  • O item de trabalho simula uma tarefa em segundo plano de execução longa:The work item simulates a long-running background task:
    • Três atrasos de 5 segundos são executados ( Task.Delay ).Three 5-second delays are executed (Task.Delay).
    • Uma try-catch instrução intercepta OperationCanceledException se a tarefa é cancelada.A try-catch statement traps OperationCanceledException if the task is cancelled.
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 =>
                {
                    // Simulate three 5-second tasks to complete
                    // for each enqueued work item

                    int delayLoop = 0;
                    var guid = Guid.NewGuid().ToString();

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

                    while (!token.IsCancellationRequested && delayLoop < 3)
                    {
                        try
                        {
                            await Task.Delay(TimeSpan.FromSeconds(5), token);
                        }
                        catch (OperationCanceledException)
                        {
                            // Prevent throwing if the Delay is cancelled
                        }

                        delayLoop++;

                        _logger.LogInformation(
                            "Queued Background Task {Guid} is running. " +
                            "{DelayLoop}/3", guid, delayLoop);
                    }

                    if (delayLoop == 3)
                    {
                        _logger.LogInformation(
                            "Queued Background Task {Guid} is complete.", guid);
                    }
                    else
                    {
                        _logger.LogInformation(
                            "Queued Background Task {Guid} was cancelled.", guid);
                    }
                });
            }
        }
    }
}

Os serviços são registrados em IHostBuilder.ConfigureServices (Program.cs).The services are registered in IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado está registrado com o AddHostedService método de extensão:The hosted service is registered with the AddHostedService extension method:

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

MonitorLoopé iniciado em Program.Main :MonitorLoop is started in Program.Main:

var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();

No ASP.NET Core, as tarefas em segundo plano podem ser implementadas como serviços hospedados.In ASP.NET Core, background tasks can be implemented as hosted services. Um serviço hospedado é uma classe com lógica de tarefa em segundo plano que implementa a interface IHostedService.A hosted service is a class with background task logic that implements the IHostedService interface. Este tópico fornece três exemplos de serviço hospedado:This topic provides three hosted service examples:

Exibir ou baixar código de exemplo (como baixar)View or download sample code (how to download)

PacotePackage

Referencie o metapacote Microsoft.AspNetCore.App ou adicione uma referência de pacote ao pacote Microsoft.Extensions.Hosting.Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Hosting package.

Interface IHostedServiceIHostedService interface

Os serviços hospedados implementam a interface IHostedService.Hosted services implement the IHostedService interface. A interface define dois métodos para objetos que são gerenciados pelo host:The interface defines two methods for objects that are managed by the host:

  • StartAsync (CancellationToken): StartAsync contém a lógica para iniciar a tarefa em segundo plano.StartAsync(CancellationToken): StartAsync contains the logic to start the background task. Ao usar o host Web, StartAsync é chamado depois que o servidor é iniciado e IApplicationLifetime. ApplicationStarted é disparado.When using the Web Host, StartAsync is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered. Ao usar o host genérico, StartAsync é chamado antes de ApplicationStarted ser disparado.When using the Generic Host, StartAsync is called before ApplicationStarted is triggered.

  • StopAsync (CancellationToken): disparado quando o host está executando um desligamento normal.StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync contém a lógica para encerrar a tarefa em segundo plano.StopAsync contains the logic to end the background task. Implemente o IDisposable e os finalizadores (destruidores) para descartar todos os recursos não gerenciados.Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    O token de cancelamento tem um tempo limite padrão de cinco segundos para indicar que o processo de desligamento não deve mais ser normal.The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. Quando for solicitado um cancelamento no token:When cancellation is requested on the token:

    • Todas as demais operações em segundo plano que o aplicativo estiver executando deverão ser anuladas.Any remaining background operations that the app is performing should be aborted.
    • Todos os métodos chamados em StopAsync deverão retornar imediatamente.Any methods called in StopAsync should return promptly.

    No entanto, as tarefas não são abandonadas após a solicitação de cancelamento—o chamador aguarda a conclusão de todas as tarefas.However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    Se o aplicativo for desligado inesperadamente (por exemplo, em uma falha do processo do aplicativo), StopAsync não poderá ser chamado.If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. Portanto, os métodos chamados ou operações realizadas em StopAsync talvez não ocorram.Therefore, any methods called or operations conducted in StopAsync might not occur.

    Para estender o tempo limite de desligamento padrão de cinco segundos, defina:To extend the default five second shutdown timeout, set:

O serviço hospedado é ativado uma única vez na inicialização do aplicativo e desligado normalmente durante o desligamento do aplicativo.The hosted service is activated once at app startup and gracefully shut down at app shutdown. Se um erro for gerado durante a execução da tarefa em segundo plano, Dispose deverá ser chamado mesmo se StopAsync não for chamado.If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

Tarefas em segundo plano temporizadasTimed background tasks

Uma tarefa em segundo plano temporizada usa a classe System.Threading.Timer.A timed background task makes use of the System.Threading.Timer class. O temporizador dispara o método DoWork da tarefa.The timer triggers the task's DoWork method. O temporizador é desabilitado em StopAsync e descartado quando o contêiner de serviço é descartado em 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();
    }
}

O Timer não aguarda as execuções anteriores do DoWork serem concluídas, portanto, a abordagem mostrada pode não ser adequada para cada cenário.The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario.

O serviço está registrado em Startup.ConfigureServices com o método de extensão AddHostedService:The service is registered in Startup.ConfigureServices with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

Consumindo um serviço com escopo em uma tarefa em segundo planoConsuming a scoped service in a background task

Para usar serviços com escopo dentro de um IHostedService , crie um escopo.To use scoped services within an IHostedService, create a scope. Por padrão, nenhum escopo é criado para um serviço hospedado.No scope is created for a hosted service by default.

O serviço da tarefa em segundo plano com escopo contém a lógica da tarefa em segundo plano.The scoped background task service contains the background task's logic. No exemplo a seguir, um ILogger é injetado no serviço: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.");
    }
}

O serviço hospedado cria um escopo para resolver o serviço da tarefa em segundo plano com escopo para chamar seu 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;
    }
}

Os serviços são registrados em Startup.ConfigureServices.The services are registered in Startup.ConfigureServices. A implementação IHostedService é registrada com o método de extensão AddHostedService:The IHostedService implementation is registered with the AddHostedService extension method:

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

Tarefas em segundo plano na filaQueued background tasks

Uma fila de tarefas em segundo plano é baseada no .NET Framework 4. x QueueBackgroundWorkItem (provisoriamente agendado para ser interno para ASP.NET Core):A background task queue is based on the .NET Framework 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;
    }
}

No QueueHostedService, as tarefas em segundo plano na fila são removidas da fila e executadas como um BackgroundService, que é uma classe base para implementar um IHostedService de longa execução: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.");
    }
}

Os serviços são registrados em Startup.ConfigureServices.The services are registered in Startup.ConfigureServices. A implementação IHostedService é registrada com o método de extensão AddHostedService:The IHostedService implementation is registered with the AddHostedService extension method:

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

Na classe de modelo de página de índice:In the Index page model class:

  • O IBackgroundTaskQueue é injetado no construtor e atribuído a Queue.The IBackgroundTaskQueue is injected into the constructor and assigned to Queue.
  • Um IServiceScopeFactory é injetado e atribuído a _serviceScopeFactory.An IServiceScopeFactory is injected and assigned to _serviceScopeFactory. O alocador é usado para criar instâncias de IServiceScope, que é usado para criar serviços dentro de um escopo.The factory is used to create instances of IServiceScope, which is used to create services within a scope. Um escopo é criado para usar o aplicativo AppDbContext (um serviço com escopo) para gravar registros de banco de dados IBackgroundTaskQueue (um serviço 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; }

Quando o botão Adicionar Tarefa é selecionado na página de índice, o método OnPostAddTask é executado.When the Add Task button is selected on the Index page, the OnPostAddTask method is executed. QueueBackgroundWorkItemé chamado para enfileirar um item de trabalho: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 adicionaisAdditional resources