Partilhar via


Implementar tarefas em segundo plano em microsserviços com IHostedService e a classe BackgroundService

Gorjeta

Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Tarefas em segundo plano e trabalhos agendados são algo que você pode precisar usar em qualquer aplicativo, independentemente de seguir ou não o padrão de arquitetura de microsserviços. A diferença ao usar uma arquitetura de microsserviços é que você pode implementar a tarefa em segundo plano em um processo/contêiner separado para hospedagem, para que possa reduzi-la/aumentar com base em sua necessidade.

De um ponto de vista genérico, no .NET chamamos esse tipo de tarefas de Serviços Hospedados, porque são serviços/lógica que você hospeda dentro do seu host/aplicativo/microsserviço. Observe que, nesse caso, o serviço hospedado significa simplesmente uma classe com a lógica de tarefa em segundo plano.

Desde o .NET Core 2.0, a estrutura fornece uma nova interface chamada IHostedService ajudando você a implementar facilmente serviços hospedados. A ideia básica é que você pode registrar várias tarefas em segundo plano (serviços hospedados) que são executadas em segundo plano enquanto seu host ou host está em execução, como mostrado na imagem 6-26.

Diagram comparing ASP.NET Core IWebHost and .NET Core IHost.

Figura 6-26. Usando IHostedService em um WebHost vs. um Host

ASP.NET Suporte IWebHost Core 1.x e 2.x para processos em segundo plano em aplicativos Web. O .NET Core 2.1 e versões posteriores oferecem suporte IHost para processos em segundo plano com aplicativos de console simples. Observe a diferença feita entre WebHost e Host.

A WebHost (implementação IWebHostde classe base) no ASP.NET Core 2.0 é o artefato de infraestrutura que você usa para fornecer recursos de servidor HTTP ao seu processo, como quando você está implementando um aplicativo Web MVC ou um serviço de API Web. Ele fornece toda a bondade da nova infraestrutura no ASP.NET Core, permitindo que você use injeção de dependência, insira middlewares no pipeline de solicitação e similares. O WebHost usa estes mesmos IHostedServices para tarefas em segundo plano.

A Host (implementação IHostde classe base) foi introduzida no .NET Core 2.1. Basicamente, a Host permite que você tenha uma infraestrutura semelhante à que você tem com WebHost (injeção de dependência, serviços hospedados, etc.), mas neste caso, você só quer ter um processo simples e mais leve como o host, sem nada relacionado a MVC, API Web ou recursos de servidor HTTP.

Portanto, você pode escolher e criar um processo de host especializado com para lidar com IHost os serviços hospedados e nada mais, como um microsserviço feito apenas para hospedar o IHostedServices, ou você pode, alternativamente, estender um ASP.NET Core WebHostexistente, como um aplicativo ASP.NET Core Web API ou MVC existente.

Cada abordagem tem prós e contras, dependendo do seu negócio e das necessidades de escalabilidade. A linha inferior é basicamente que, se suas tarefas em segundo plano não têm nada a ver com HTTP (IWebHost), você deve usar IHost.

Registrando serviços hospedados em seu WebHost ou Host

Vamos detalhar mais sobre a interface, IHostedService já que seu uso é bastante semelhante em um WebHost ou em um Host.

O SignalR é um exemplo de artefato usando serviços hospedados, mas você também pode usá-lo para coisas muito mais simples, como:

  • Uma tarefa em segundo plano sondando um banco de dados à procura de alterações.
  • Uma tarefa agendada atualizando algum cache periodicamente.
  • Uma implementação de QueueBackgroundWorkItem que permite que uma tarefa seja executada em um thread em segundo plano.
  • Processar mensagens de uma fila de mensagens em segundo plano de um aplicativo Web enquanto compartilha serviços comuns, como ILogger.
  • Uma tarefa em segundo plano começou com Task.Run().

Você pode basicamente descarregar qualquer uma dessas ações para uma tarefa em segundo plano que implementa IHostedServiceo .

A maneira como você adiciona um ou vários IHostedServices ao seu WebHost ou Host é registrando-os através do AddHostedService método de extensão em um ASP.NET Core WebHost (ou em um Host no .NET Core 2.1 e superior). Basicamente, você tem que registrar os serviços hospedados dentro da inicialização do aplicativo em Program.cs.

//Other DI registrations;

// Register Hosted Services
builder.Services.AddHostedService<GracePeriodManagerService>();
builder.Services.AddHostedService<MyHostedServiceB>();
builder.Services.AddHostedService<MyHostedServiceC>();
//...

Nesse código, o serviço hospedado é o GracePeriodManagerService código real do microsserviço de negócios Ordering no eShopOnContainers, enquanto os outros dois são apenas dois exemplos adicionais.

A IHostedService execução da tarefa em segundo plano é coordenada com o tempo de vida do aplicativo (host ou microsserviço). Você registra tarefas quando o aplicativo é iniciado e tem a oportunidade de fazer alguma ação normal ou limpeza quando o aplicativo está sendo desligado.

Sem usar IHostedServiceo , você sempre pode iniciar um thread em segundo plano para executar qualquer tarefa. A diferença está justamente no momento de desligamento do aplicativo, quando esse thread simplesmente seria morto sem ter a oportunidade de executar ações de limpeza graciosas.

A interface IHostedService

Quando você registra um IHostedService, o .NET chama os métodos e StopAsync() do seu IHostedService tipo durante o StartAsync() início e a parada do aplicativo, respectivamente. Para obter mais detalhes, consulte Interface IHostedService.

Como você pode imaginar, você pode criar várias implementações de IHostedService e registrar cada uma delas em Program.cs, como mostrado anteriormente. Todos esses serviços hospedados serão iniciados e interrompidos junto com o aplicativo/microsserviço.

Como desenvolvedor, você é responsável por lidar com a ação de parada de seus serviços quando StopAsync() o método é acionado pelo host.

Implementando IHostedService com uma classe de serviço hospedada personalizada derivada da classe base BackgroundService

Você pode ir em frente e criar sua classe de serviço hospedada personalizada do zero e implementar o , como você precisa fazer ao usar o IHostedService.NET Core 2.0 e posterior.

No entanto, como a maioria das tarefas em segundo plano terá necessidades semelhantes em relação ao gerenciamento de tokens de cancelamento e outras operações típicas, há uma classe base abstrata conveniente da qual você pode derivar, nomeada BackgroundService (disponível desde .NET Core 2.1).

Essa classe fornece o trabalho principal necessário para configurar a tarefa em segundo plano.

O próximo código é a classe base abstrata BackgroundService conforme implementado no .NET.

// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
    private Task _executingTask;
    private readonly CancellationTokenSource _stoppingCts =
                                                   new CancellationTokenSource();

    protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        // Store the task we're executing
        _executingTask = ExecuteAsync(_stoppingCts.Token);

        // If the task is completed then return it,
        // this will bubble cancellation and failure to the caller
        if (_executingTask.IsCompleted)
        {
            return _executingTask;
        }

        // Otherwise it's running
        return Task.CompletedTask;
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        // Stop called without start
        if (_executingTask == null)
        {
            return;
        }

        try
        {
            // Signal cancellation to the executing method
            _stoppingCts.Cancel();
        }
        finally
        {
            // Wait until the task completes or the stop token triggers
            await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
                                                          cancellationToken));
        }

    }

    public virtual void Dispose()
    {
        _stoppingCts.Cancel();
    }
}

Ao derivar da classe base abstrata anterior, graças a essa implementação herdada, você só precisa implementar o ExecuteAsync() método em sua própria classe de serviço hospedada personalizada, como no código simplificado a seguir do eShopOnContainers, que está sondando um banco de dados e publicando eventos de integração no Barramento de Eventos quando necessário.

public class GracePeriodManagerService : BackgroundService
{
    private readonly ILogger<GracePeriodManagerService> _logger;
    private readonly OrderingBackgroundSettings _settings;

    private readonly IEventBus _eventBus;

    public GracePeriodManagerService(IOptions<OrderingBackgroundSettings> settings,
                                     IEventBus eventBus,
                                     ILogger<GracePeriodManagerService> logger)
    {
        // Constructor's parameters validations...
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogDebug($"GracePeriodManagerService is starting.");

        stoppingToken.Register(() =>
            _logger.LogDebug($" GracePeriod background task is stopping."));

        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogDebug($"GracePeriod task doing background work.");

            // This eShopOnContainers method is querying a database table
            // and publishing events into the Event Bus (RabbitMQ / ServiceBus)
            CheckConfirmedGracePeriodOrders();

            try {
                    await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
                }
            catch (TaskCanceledException exception) {
                    _logger.LogCritical(exception, "TaskCanceledException Error", exception.Message);
                }
        }

        _logger.LogDebug($"GracePeriod background task is stopping.");
    }

    .../...
}

Neste caso específico para eShopOnContainers, ele está executando um método de aplicativo que está consultando uma tabela de banco de dados procurando pedidos com um estado específico e, ao aplicar alterações, está publicando eventos de integração por meio do barramento de eventos (abaixo dele pode estar usando RabbitMQ ou Azure Service Bus).

Claro, você pode executar qualquer outra tarefa de plano de fundo de negócios, em vez disso.

Por padrão, o token de cancelamento é definido com um tempo limite de 5 segundos, embora você possa alterar esse valor ao criar o UseShutdownTimeout uso WebHost da IWebHostBuilderextensão do . Isto significa que o nosso serviço deverá ser cancelado dentro de 5 segundos, caso contrário será eliminado de forma mais abrupta.

O código a seguir estaria alterando esse tempo para 10 segundos.

WebHost.CreateDefaultBuilder(args)
    .UseShutdownTimeout(TimeSpan.FromSeconds(10))
    ...

Diagrama de classe de resumo

A imagem a seguir mostra um resumo visual das classes e interfaces envolvidas na implementação do IHostedServices.

Diagram showing that IWebHost and IHost can host many services.

Figura 6-27. Diagrama de classes mostrando as várias classes e interfaces relacionadas a IHostedService

Diagrama de classes: IWebHost e IHost podem hospedar muitos serviços, que herdam de BackgroundService, que implementa IHostedService.

Considerações e conclusões sobre a implantação

É importante observar que a maneira como você implanta seu ASP.NET Core WebHost ou .NET Host pode afetar a solução final. Por exemplo, se você implantar o IIS WebHost ou um Serviço de Aplicativo do Azure regular, seu host poderá ser desligado devido às reciclagens do pool de aplicativos. Mas se você estiver implantando seu host como um contêiner em um orquestrador como o Kubernetes, poderá controlar o número garantido de instâncias ao vivo do seu host. Além disso, você pode considerar outras abordagens na nuvem especialmente feitas para esses cenários, como o Azure Functions. Finalmente, se você precisar que o serviço esteja em execução o tempo todo e estiver implantando em um Windows Server, poderá usar um Serviço do Windows.

Mas mesmo para um WebHost implantado em um pool de aplicativos, há cenários como repovoar ou liberar o cache na memória do aplicativo que ainda seriam aplicáveis.

A IHostedService interface fornece uma maneira conveniente de iniciar tarefas em segundo plano em um aplicativo Web ASP.NET Core (no .NET Core 2.0 e versões posteriores) ou em qualquer processo/host (começando no .NET Core 2.1 com IHost). Seu principal benefício é a oportunidade que você tem com o cancelamento gracioso de limpar o código de suas tarefas em segundo plano quando o próprio host está desligando.

Recursos adicionais