C# ASP .NET .NET Core

.NET Core 3.0 - Criando tarefas de segundo plano com Worker Service

Você já quis criar tarefas em segundo plano com o .NET Core? Na versão 3.0 foi introduzido o template Worker Service que facilita a criação deste tipo de aplicação. Caso tenha passado por uma situação assim, neste artigo mostramos como você pode aproveitar este novo recurso do .NET Core 3.0.

há 4 anos 8 meses

Formação Desenvolvedor C#
Conheça a formação em detalhes

Quando se pensa em uma aplicação ASP.NET Core, provavelmente irá pensar em uma aplicação MVC, Web API ou Razor. Mas um detalhe pouco conhecido é que também é possível criar tarefas de segundo plano com o ASP.NET Core, através do conceito de generic hosts. Na versão 3.0 do .NET Core, este tipo de aplicação foi definida em um novo template chamado Worker Service, facilitando assim a criação das tarefas de segundo plano e dando mais destaque para este recurso.

O conceito de generic host foi introduzido na versão 2.1 do .NET Core e trata-se de uma aplicação ASP.NET Core que não processa requisições HTTP. O objetivo deste tipo de aplicação é remover o pipeline HTTP enquanto mantém os demais recursos de uma aplicação ASP.NET Core, como configuração, injeção de dependência e logging. Permitindo assim a criação de aplicações que precisam ser executadas em segundo plano, como serviços de mensagens.

C# (C Sharp) Básico
Curso C# (C Sharp) Básico
Conhecer o curso

Criando uma aplicação Worker Service

Para criar uma aplicação Worker Service é necessário ter instalado o .NET Core 3.0, que no momento da criação deste artigo está no preview 7. Caso esteja utilizando o Visual Studio, este tipo de aplicação está disponível a partir da versão 2019.

Como estou utilizando o Visual Studio Code, vou criar a aplicação pela linha de comando com o código abaixo:

dotnet new worker -n WorkerSample

No Visual Studio 2019 você pode encontrar o template desta aplicação dentro dos subtemplates do ASP.NET Core.

Na classe Program, podemos notar que é criado um servidor web:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });
}

Que define um serviço, a classe Worker, que veremos a seguir.

Definindo a tarefa

A classe responsável por executar a tarefa em segundo plano é a classe Worker:

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

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

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);
        }
    }
}

Note que o construtor recebe uma instância de Logger. Podemos utilizar esta instância para notificar o usuário do estado do serviço. Já no método ExecuteAsync é onde a tarefa de segundo plano é executada.

Note que ela é executada enquanto o serviço não for cancelado.

Para este artigo iremos modificar apenas o método ExecuteAsync, adicionando informações sobre o estado do serviço:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("O serviço está iniciando.");

    stoppingToken.Register(() => _logger.LogInformation("Tarefa de segundo plano está parando."));

    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("Executando tarefa: {time}", DateTimeOffset.Now);
        await Task.Delay(10000, stoppingToken);
    }

    _logger.LogInformation("O serviço está parando.");
}

Ao executar a aplicação temos o resultado abaixo:

No momento ela ainda não está sendo executada como serviço, vamos configurá-la para isso.

C# (C Sharp) Intermediário
Curso C# (C Sharp) Intermediário
Conhecer o curso

Configurando a tarefa como serviço para ser executada em segundo plano

Para executar a aplicação como serviço, é necessário adicionar o pacote “Microsoft.Extensions.Hosting.WindowsServices” na aplicação:

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

Ao adicioná-lo podemos chamar o método UseWindowsService():

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Worker>();
        });

Este método verifica se a aplicação está executando como serviço e faz as configurações necessárias de acordo deste contexto. Ela também configura a aplicação para utilizar o ServiceBaseLifetime , que auxilia no controle do ciclo de vida da aplicação enquanto ela é executada como serviço. Isso sobrescreve o padrão ConsoleLifetime.

Publicando o serviço

Com o ServiceBaseLifetime configurado, podemos publicar a nossa aplicação:

dotnet publish -c Release

O comando acima irá publicar a aplicação de acordo com o ambiente de desenvolvimento. Você também pode publicar a aplicação para um sistema específico com a opção -r:

dotnet publish -c Release -r <RID>

`` significa Runtime Identifier (RID) e na documentação você pode ver as opções disponíveis.

Instalando o serviço no Windows

Com a aplicação publicada, ela pode ser instalada como serviço no Windows com o utilitário sc:

sc create workersample binPath=C:\Programs\Services\WorkerSample.exe

Instalando o serviço no Mac OS X

No caso do Mac OS X, os serviços são conhecimentos como daemon. Para definir o nosso serviço neste sistema como um daemon, é necessário possuir o .NET Core instalado na máquina e definir um script:

#!bin/bash
#Start WorkerSample if not running
if [ “$(ps -ef | grep -v grep | grep WorkerSample | wc -l)” -le 0 ]
then
 dotnet /usr/local/services/WorkerSample.dll
 echo "WorkerSampler Started"
else
 echo "WorkerSample Already Running"
fi

Dê para este script permissão de execução:

chmod +x ~/scripts/startup/startup.sh

Os daemons neste sistema operacional são definidos na pasta /Library/LaunchDaemons/ , onde deve ser configurado em um arquivo XML com a extensão .plist (não faz parte do escopo deste artigo explicar as configurações deste arquivo, você pode conhecê-las na documentação do launchd).

No nosso caso, o arquivo terá o conteúdo abaixo:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>EnvironmentVariables</key>
    <dict>
      <key>PATH</key>
      <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:</string>
    </dict>
    <key>Label</key>
    <string>br.com.treinaweb.workersample</string>
    <key>Program</key>
    <string>/Users/admin/scripts/startup/startup.sh</string>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
    <key>LaunchOnlyOnce</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/tmp/startup.stdout</string>
    <key>StandardErrorPath</key>
    <string>/tmp/startup.stderr</string>
    <key>UserName</key>
    <string>admin</string>
    <key>GroupName</key>
    <string>admin</string>
    <key>InitGroups</key>
    <true/>
  </dict>
</plist>

Com isso, para iniciar o daemon basta carregar o arquivo .plist, que acabamos de definir:

sudo launchctl load -w /Library/LaunchDaemons/br.com.treinaweb.workersample.plist

Instalando o daemon no Linux

Assim como no Mac OS X, no Linux os serviços são conhecidos como daemon e nesta plataforma também é necessário possuir o .NET Core instalado na máquina. Fora isso, cada distribuição pode definir uma forma de criação de daemon, aqui vou explicar a criação utilizando o systemd.

No systemd o serviço é definido em um arquivo .service salvo em /etc/systemd/system e que no exemplo deste artigo conterá o conteúdo o abaixo:

[Unit]
Description=Dotnet Core Demo service
  
[Service]  
ExecStart=/bin/dotnet/dotnet WorkerSample.dll
WorkingDirectory=/usr/local/services
User=dotnetuser  
Group=dotnetuser  
Restart=on-failure  
SyslogIdentifier=dotnet-sample-service  
PrivateTmp=true  
  
[Install]  
WantedBy=multi-user.target  

Após isso, basta habilitar o daemon:

systemctl enable worker-sample.service 

Que ele poderá ser iniciado:

systemctl start dotnet-sample-service.service

Obs: Infelizmente no Mac OS X e Linux, a aplicação não sabe quando está sendo parada, algo que ocorre no Windows, quando ela é executada como serviço.

C# (C Sharp) Avançado
Curso C# (C Sharp) Avançado
Conhecer o curso

O Worker Service é bom?

O template Worker Service é uma boa adição para o .NET Core, pois isso pode tornar os generic hosts cada vez mais populares. Permitindo que as pessoas consigam utilizar os recursos do ASP.NET Core em uma aplicação console.

E com o tempo, espero que melhore o suporte da aplicação como daemon em ambientes Unix.

Autor(a) do artigo

Wladimilson M. Nascimento
Wladimilson M. Nascimento

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

Todos os artigos

Artigos relacionados Ver todos