Hoje em dia, existem diversas soluções para desenvolver um Windows Service que execute de tempo em tempo, cada uma com sua peculiaridade e maneira de configurar.
Pensando na simplicidade de desenvolvimento e facilidade de instalação, selecionamos alguns recursos que juntos formam uma espécie de canivete suíço para esse tipo de aplicação: TopShelf e Timer em .NET Core 2.2 / 3.0.
Iremos criar, configurar e instalar em nossa máquina um pequeno projeto de testes utilizando esses recursos.

Criando a aplicação de testes
O projeto base que usaremos será um Console Application baseado no .NET Core 3.0 ou 2.2. Para esse exemplo, iremos escolher a versão 3.0, a mais recente.
Para configurar um projeto seguindo essas especificações, inicie seu Visual Studio, vá em em Create a new Project, selecione a opção Console App (.NET Core).
Na próxima tela, você poderá especificar o nome do projeto, bem como o seu diretório. Após concluir a configuração, teremos o projeto pronto para iniciarmos o desenvolvimento!
Na próxima tela, você poderá especificar o nome do projeto, bem como o seu diretório. Após concluir a configuração, teremos o projeto pronto para iniciarmos o desenvolvimento!
Instalando o TopShelf
Através do Manage NuGet Packages, iremos adicionar as dependências do TopShelf no nosso projeto:
A versão escolhida foi a 4.2.1, a última estável até então!
Criando classe de controle do serviço
Após concluirmos a instalação, iremos criar uma nova pasta no projeto principal chamada Infrastructure e adicionar uma nova classe responsável por controlar o início do serviço TopShelf. A chamaremos de ServiceBaseLifetime.
- · Infrastructure
-
- ServiceBaseLifetime.cs
-
A ServiceBaseLifetime implementará a interface ServiceControl do TopShelf, que nos indica utilizar dois novos métodos que darão vida ao nosso serviço: Start e Stop, ambos recebendo como parâmetro o HostControl que possibilita ajustar alguns detalhes do host, falaremos mais sobre ele em outra oportunidade.
Como o próprio nome já sugere, o método Start será chamado toda vez que o Windows Service for inicializado pelo sistema operacional, enquanto que o Stop, toda vez que for parado.
Veja como ficará a implementação da ServiceBaseLifetime após implementarmos a interface:
using System;
using Topshelf;
namespace ProjetoTesteTopShelf.Infrastructure
{
public class ServiceBaseLifetime: ServiceControl
{
private async Task ExecuteAsync()
{
}
public bool Stop(HostControl hostControl)
{
}
}
}
Configurando o Timer
Caso quiséssemos que nosso Windows Service executasse apenas uma vez, poderíamos simplesmente implementar as funções do nosso serviço dentro da classe Start, mas a ideia não era termos uma lógica que executasse de tempo em tempo?
Para isso, precisaremos configurar algum tipo de recurso que controle essa execução. O escolhido da vez é o Timer (System.Threading.Timer)!
A ideia é utilizarmos o método Start para instanciar um novo Timer, ou seja, toda vez que o Windows Service for inicializado, ele irá configurar um novo Timer que receberá a frequência com que ele deve executar as funções e outros detalhes.
Para organizarmos melhor nossa classe, criaremos um novo método chamado ExecuteAsync! Ele que será chamado pelo Timer e guardará o que deve ser executado de tempo em tempo.
Já vamos aproveitar e retornar true no método Start e Stop, indicando que o TopShelf poderá iniciar ou parar o Windows Service, respectivamente.
Veja um exemplo, onde configuramos para o serviço executar a cada 60 segundos:
using System;
using System.Threading;
using System.Threading.Tasks;
using Topshelf;
namespace ProjetoTesteTopShelf.Infrastructure
{
public class ServiceBaseLifetime: ServiceControl
{
private Timer _timer;
public bool Start(HostControl hostControl)
{
_timer = new Timer(callback: async c => await ExecuteAsync(),
//State: Objeto que carrega informações que podem ser utilizadas pelo método de callback (ExecuteAsync)
state: null,
//dueTime: Delay para inicializar o ExecuteAsync
dueTime: TimeSpan.Zero,
//Period: Frequência que o ExecuteAsync deverá ser executado
period: TimeSpan.FromSeconds(60));
return true;
}
private async Task ExecuteAsync()
{
//Execução da lógica aqui
}
public bool Stop(HostControl hostControl)
{
return true;
}
private async Task ExecuteAsync()
{
//Execução da lógica aqui
}
public bool Stop(HostControl hostControl)
{
return true;
}
}
}
Percebeu que o método é assíncrono, não é mesmo? Pois é, isso nos possibilita utilizarmos async/await na execução.
Evitando execuções concorrentes (Opcional)
Se continuarmos o desenvolvimento mantendo a classe ServiceBaseLifetime do jeito que esta, tudo indica que funcionará, certo? Sim! Mas tem um detalhe importante que já vamos nos previnir aqui: concorrência!
Da maneira como está desenvolvido, se uma das execuções demorar mais do que 60 segundos, a segunda inicializará enquanto a primeira ainda é executada.
E se quisermos que a seguinte só inicie quando a anterior finalizar? Podemos fazer isso da seguinte maneira: Toda vez que uma execução inicializar, alteraremos a frequência do Timer para infinito, e quando finalizar, voltaremos para a frequência de 60 em 60 segundos:
private async Task ExecuteAsync()
{
try
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
//Execução da lógica aqui
}
finally
{
_timer.Change(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
}
}
Chamando nosso ServiceBaseLifetime
O último passo agora é configurar a classe Program para chamar nosso ServiceBaseLifetime.
Iniciaremos alterando a declaração do método Main para ser assíncrono:
public static async Task Main(string[] args)
Agora, iremos criar um Host para conseguirmos injetar a dependência do ServiceBaseLifetime. Para isso, precisaremos instalar via Manage NuGet Package o Microsoft.Extensions.Hosting e utilizar o seu HostBuilder. Ele deve ser instanciado da seguinte maneira, já configurando o ServiceBaseLifetime:
var host = new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices((hostBuilderContext, services) =>
{
services.AddSingleton(typeof(ServiceBaseLifetime));
});
Logo depois, utilizando o HostFactory, iremos configurar o TopShelf para chamar os métodos Start e Stop do ServiceBaseLifetime.
Também já iremos aproveitar para configurar o reinício automático do serviço caso aconteça algum problema. Além disso, já iremos definir os nomes e as descrições que aparecerão nos serviços do Windows. Veja a implementação completa dessa classe:
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ProjetoTesteTopShelf.Infrastructure;
using Topshelf;
namespace ProjetoTesteTopShelf
{
public class Program
{
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices((hostBuilderContext, services) =>
{
services.AddSingleton(typeof(ServiceBaseLifetime));
});
HostFactory.Run(x =>
{
x.Service<ServiceBaseLifetime>(sc =>
{
sc.ConstructUsing(s =>
host.Build().Services.GetRequiredService<ServiceBaseLifetime>());
sc.WhenStarted((s, c) => s.Start(c));
sc.WhenStopped((s, c) => s.Stop(c));
});
x.RunAsLocalSystem()
.DependsOnEventLog()
.StartAutomatically()
.EnableServiceRecovery(rc => rc.RestartService(1));
x.SetDescription(“Testes do TopShelf”);
x.SetDisplayName(“Projeto Teste TopShelf”);
x.SetServiceName(“ProjetoTeste.TopShelf”);
});
await Task.CompletedTask;
}
}
}
Testando o projeto
Ao executar em modo debug, se colocarmos um breakpoint dentro do método ExecuteAsync, poderemos ver que teremos a execução de minuto a minuto ocorrendo normalmente.
Instalando o serviço
Podemos também instalar o serviço localmente. Para isso, iremos publicar (botão direito no projeto, selecione Publish) o nosso serviço com as seguintes configurações:
Através do PowerShell como Administrador, abriremos a pasta onde ele publicou o nosso serviço. Nela, teremos os seguintes arquivos gerados pela nossa publicação:
Podemos instalar o serviço executando: ./{nome do projeto}.exe install
Podemos agora abrir os Serviços do Windows e ver ele na lista, pronto para ser inicializado:
Caso queira desinstalar o serviço, execute o comando ./{nome do projeto}.exe uninstall
…
Encerramos aqui este tutorial de como desenvolver, configurar e instalar um Windows Service para rodar de tempo em tempo!
Escolhemos uma de diversas possibilidades para fazer isso. Conhece outra e gostaria de compartilhar com a gente? Tem alguma sugestão? Comente e colabore com a comunidade! =)
Código fonte do projeto: https://github.com/lucastedeschi/windowsservice-topshelf-timer
Abraço!