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

Dica

Esse conteúdo é um trecho do eBook, arquitetura de microsserviços do .NET para aplicativos .NET em contêineres, 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 talvez você precise usar em qualquer aplicativo, seja seguindo 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 você possa dimensioná-la para baixo/para cima com base em suas necessidades.

Do ponto de vista genérico, no .NET, chamamos esse tipo de tarefas de Serviços Hospedados, pois são serviços/lógica que você hospeda em seu host/aplicativo/microsserviço. Observe que, neste caso, o serviço hospedado simplesmente significa 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 serviços hospedados facilmente. 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 da Web está em execução, conforme mostrado na imagem 6-26.

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

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

ASP.NET Core suporte IWebHost 1.x e 2.x para processos em segundo plano em aplicativos Web. O .NET Core 2.1 e versões posteriores dão suporte IHost a processos em segundo plano com aplicativos de console sem formatação. Observe a diferença entre WebHost e Host.

A WebHost (implementação de IWebHostclasse 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 serviço de API Web. Ele fornece todas as novas funcionalidades de infraestrutura em ASP.NET Core, permitindo que você use a injeção de dependência, insira middlewares no pipeline de solicitação e similares. O WebHost uso é o mesmo IHostedServices para tarefas em segundo plano.

Um Host (classe base que implementa IHost) foi introduzido no .NET Core 2.1. Basicamente, um Host permite que você tenha uma infraestrutura semelhante àquela que você tem com WebHost (injeção de dependência, serviços hospedados etc.), mas, nesse caso, você quer apenas ter um processo simples e mais leve como o host, sem nada relacionado ao MVC, à API da Web ou aos recursos de servidor HTTP.

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

Cada abordagem tem vantagens e desvantagens, dependendo de suas necessidades de negócios e escalabilidade. A linha de fundo é basicamente que, se suas tarefas em segundo plano não tiverem nada a ver com HTTP (IWebHost) você deve usar IHost.

Registro de serviços hospedados em seu WebHost ou Host

Vamos fazer uma busca detalhada na interface, pois seu IHostedService uso é bastante semelhante em um WebHost ou em um Host.

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

  • Uma tarefa em segundo plano sondando um banco de dados em busca 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.
  • Processamento de mensagens de uma fila de mensagens em segundo plano de um aplicativo Web enquanto se compartilham serviços comuns como ILogger.
  • Uma tarefa em segundo plano iniciada com Task.Run().

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

A maneira como você adiciona um ou vários IHostedServices ao seu WebHost ou Host é registrando-os por meio do AddHostedService método de extensão em um ASP.NET Core WebHost (ou em um Host .NET Core 2.1 e superior). Basicamente, você deve registrar os serviços hospedados no método ConfigureServices() familiar da classe Startup, como no código a seguir de um WebHost ASP.NET típico.

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    //Other DI registrations;

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

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

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

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

A interface IHostedService

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

Como você pode imaginar, é possível criar várias implementações de IHostedService e registrá-las no método ConfigureService() no contêiner de ID, 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 o método StopAsync() é disparado pelo host.

Implementando IHostedService com uma classe de serviço hospedado personalizado derivado da classe base BackgroundService

Você pode ir em frente e criar sua classe de serviço hospedada personalizada do zero e implementar a IHostedService, como você precisa fazer ao usar o .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, chamada BackgroundService (disponível no .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 backgroundservice abstrata, 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 àquela implementação herdada, você só precisa implementar o método ExecuteAsync() em sua própria classe de serviço hospedado personalizada, como no seguinte código simplificado 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();

            await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
        }

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

    .../...
}

Neste caso específico para eShopOnContainers, que 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 as alterações, ele está publicando eventos de integração por meio de um barramento de evento (sob ele, pode estar usando RabbitMQ ou Barramento de Serviço do Azure).

Obviamente, você pode executar qualquer outra tarefa em segundo plano 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 sua WebHost extensão usando a IWebHostBuilderUseShutdownTimeout . Isso significa que o nosso serviço deve cancelar dentro de 5 segundos, caso contrário, ele será encerrado mais abruptamente.

O código a seguir alteraria 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 ao implementar iHostedServices.

Diagram showing that IWebHost and IHost can host many services.

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

Diagrama de classe: o IWebHost e o IHost podem hospedar muitos serviços, herdados de BackgroundService, que implementa o IHostedService.

Pontos importantes e considerações sobre 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 WebHost no IIS ou em um Serviço de Aplicativo do Azure normal, o host poderá desligar devido a 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 seguro de instâncias dinâmicas do host. Além disso, poderá considerar outras abordagens na nuvem feitas especialmente para esses cenários, como Azure Functions. Por fim, se você precisar que o serviço esteja em execução o tempo todo e estiver implantando em um Windows Server, você poderá usar um Serviço 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 seria aplicável.

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ê obtém com o cancelamento normal para limpar o código de suas tarefas em segundo plano quando o próprio host está sendo desligado.

Recursos adicionais