No desenvolvimetno de aplicações C#, frequentemente nos deparamos com a necessidade de executar tarefas de forma assíncrona ou em horários predefinidos. Seja para melhorar a responsividade da interface do usuário, processar dados em segundo plano ou disparar rotinas diárias, a plataforma .NET oferece diversas abordagens para gerenciar essas operações. Este artigo explora métodos comuns para implementar execução assíncrona e agendada, desde os conceitos básicos de Task e async/await até uma implementação de agendador manual e a menção de soluções de terceiros.
- Execução Paralela Simples com
Task.Run
A forma mais direta de mover uma operação para um thread separado, evitando que ela bloqueie o thread principal (UI ou servidor), é utilizando Task.Run. Este método empacota uma função delegada em uma Task e a executa no Thread Pool, liberando o thread chamador. Podemos aguardar a conclusão de múltiplas tarefas usando Task.WaitAll.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Iniciando operações assíncronas com Task.Run...");
// Cria e inicia duas tarefas que simulam trabalho síncrono
Task processDataTask = Task.Run(() => PerformSynchronousWork("Processamento de Dados", 1));
Task generateReportTask = Task.Run(() => PerformSynchronousWork("Geração de Relatório", 2));
// Aguarda a conclusão de todas as tarefas iniciadas
Task.WaitAll(processDataTask, generateReportTask);
Console.WriteLine("Todas as operações síncronas em Tasks foram concluídas.");
Console.WriteLine("Pressione qualquer tecla para sair.");
Console.ReadKey(); // Mantém o console aberto
}
/// <summary>
/// Simula uma operação síncrona que leva tempo.
/// </summary>
private static void PerformSynchronousWork(string workDescription, int id)
{
Console.WriteLine($"[{id}] '{workDescription}' em execução no Thread ID: {Thread.CurrentThread.ManagedThreadId}...");
Thread.Sleep(TimeSpan.FromSeconds(2)); // Simula trabalho intensivo
Console.WriteLine($"[{id}] '{workDescription}' finalizado.");
}
}
- Gerenciamento de Fluxos Assíncronos com
asynceawait
Para operações intrinsecamente assíncronas (como I/O de rede, acesso a disco), ou para orquestrar múltiplas Tasks de forma mais legível, as palavras-chave async e await são indispensáveis. Elas permitem escrever código assíncrono que parece síncrono, melhorando a manutenção e a clareza. await Task.Delay é uma função assíncrona não bloqueante, ideal para simular atrasos sem consumir recursos do thread.
using System;
using System.Threading.Tasks;
public class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine("Preparando para executar operações assíncronas com async/await...");
// Inicia duas tarefas assíncronas que podem retornar resultados
Task<string> dataLoadTask = LoadDataAsync("Configurações do Sistema", 1);
Task<string> apiCallTask = CallApiAsync("Serviço Externo", 2);
// Aguarda a conclusão de ambas as tarefas assíncronas simultaneamente
await Task.WhenAll(dataLoadTask, apiCallTask);
// Agora que ambas terminaram, podemos obter seus resultados
string dataResult = await dataLoadTask;
string apiResult = await apiCallTask;
Console.WriteLine($"\nResultado da carga de dados: {dataResult}");
Console.WriteLine($"Resultado da chamada à API: {apiResult}");
Console.WriteLine("Todas as operações assíncronas foram finalizadas.");
Console.WriteLine("Pressione qualquer tecla para sair.");
Console.ReadKey();
}
/// <summary>
/// Simula o carregamento assíncrono de dados.
/// </summary>
private static async Task<string> LoadDataAsync(string itemName, int itemId)
{
Console.WriteLine($"[Item {itemId}] Iniciando carga de '{itemName}'...");
await Task.Delay(TimeSpan.FromSeconds(2)); // Simula um atraso assíncrono
Console.WriteLine($"[Item {itemId}] Carga de '{itemName}' concluída.");
return $"Dados de {itemName} carregados com sucesso!";
}
/// <summary>
/// Simula uma chamada assíncrona a uma API.
/// </summary>
private static async Task<string> CallApiAsync(string serviceName, int serviceId)
{
Console.WriteLine($"[Serviço {serviceId}] Iniciando chamada ao '{serviceName}'...");
await Task.Delay(TimeSpan.FromSeconds(3)); // Simula um atraso de rede/API
Console.WriteLine($"[Serviço {serviceId}] Chamada ao '{serviceName}' concluída.");
return $"Resposta do {serviceName}: OK";
}
}
- Implementando um Agendador Simples Baseado em Horário
Para cenários onde uma tarefa precisa ser executada em um horário específico e repetidamente (por exemplo, diariamente à meia-noite, ou a cada 18h), podemos construir um agendador simples usando Task.Delay. Este método calcula o tempo restante até a próxima ocorrência do horário desejado e aguarda assincronamente.
using System;
using System.Threading.Tasks;
public class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine($"Serviço de agendamento manual iniciado. Horário atual: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
const int EXECUTION_HOUR = 15; // Exemplo: 15h (3 PM)
const int EXECUTION_MINUTE = 30; // Exemplo: 30 minutos
while (true)
{
DateTime now = DateTime.Now;
DateTime nextExecutionTime = new DateTime(now.Year, now.Month, now.Day, EXECUTION_HOUR, EXECUTION_MINUTE, 0);
// Se a hora programada já passou para o dia de hoje, agende para o dia seguinte
if (now >= nextExecutionTime)
{
nextExecutionTime = nextExecutionTime.AddDays(1);
}
TimeSpan delayToWait = nextExecutionTime - now;
Console.WriteLine($"Próxima execução agendada para: {nextExecutionTime:yyyy-MM-dd HH:mm:ss}. Aguardando por {delayToWait.TotalHours:F2} horas ({delayToWait.TotalMinutes:F0} minutos)...");
await Task.Delay(delayToWait);
Console.WriteLine($"\n--- Tarefa programada ativada em: {DateTime.Now:yyyy-MM-dd HH:mm:ss} ---");
PerformScheduledWork();
Console.WriteLine("------------------------------------------\n");
// Adiciona um pequeno delay para garantir que a próxima iteração do loop
// não tente reagendar para o mesmo segundo/minuto, ou em caso de execução
// muito rápida, evitar múltiplos disparos no mesmo ciclo.
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
/// <summary>
/// Contém a lógica real da tarefa a ser executada.
/// </summary>
private static void PerformScheduledWork()
{
Console.WriteLine("Executando a lógica da tarefa agendada (ex: backup, envio de emails, etc.)...");
// Thread.Sleep(3000); // Simula um trabalho que leva tempo
Console.WriteLine("Lógica da tarefa agendada concluída.");
}
}
- Utilizando Bibliotecas de Terceiros: Quartz.NET
Para sistemas com necessidades de agendamento mais complexas e robustas, como a capacidade de agendar tarefas com base em expressões cron, persistência de jobs (sobrevivendo a reinícios da aplicação), agrupamento de tarefas, e tolerância a falhas, é recomendável o uso de bibliotecas de terceiros. Uma das opções mais populares e maduras no ecossistema .NET é o Quartz.NET. Ele oferece uma estrutura completa para definir, agendar e executar jobs, com suporte a diferentes tipos de triggers (simples, cron) e a capacidade de escalar em ambientes distribuídos. Embora o código de implementação seja mais extenso, a funcionalidade e confiabilidade superam as soluções manuais para cenários empresariais.