BackgroundService & Worker Services: .NET Core'da Arka Plan İşleri
Günümüz uygulamaları sadece web API'lerinden ibaret değildir; veritabanı temizliği, e-posta gönderimi, rapor oluşturma, kuyruk tüketimi gibi uzun soluklu, periyodik veya sürekli çalışan işlemler de vardır. ASP.NET Core'dan bağımsız olarak .NET Core, IHostedService ve BackgroundService ile bu işleri profesyonelce yönetmenize olanak tanır. Bu yazıda, Windows Servisi ve Linux systemd'den, PeriodicTimer ile modern periyodik işlemlere kadar her şeyi ele alıyoruz.
1. Temel Kavramlar: IHostedService vs BackgroundService
-
IHostedService: En temel arayüzdür.StartAsync(uygulama ayağa kalkarken) veStopAsync(uygulama kapanırken) metotlarını implemente etmenizi gerektirir. Her şeyi siz yönetirsiniz (döngü, durdurma, hata yönetimi). -
BackgroundService: Soyut (abstract) bir sınıftır veIHostedService'i implemente eder. Sizden sadeceExecuteAsyncmetotunu override etmenizi bekler. Uygulama kapanırken durdurma (stop) işlemini otomatik olarak yönetir. %99 oranındaBackgroundServicekullanılır.
Basit bir BackgroundService örneği:
csharp
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Servis başlıyor.");
// Uygulama kapanana kadar döngü (stoppingToken iptal olana kadar)
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Servis çalışıyor: {Time}", DateTimeOffset.Now);
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); // 10 saniye bekle
}
_logger.LogInformation("Servis durduruluyor.");
}
}
Kaydetme (Program.cs):
csharp
var builder = Host.CreateApplicationBuilder(args); builder.Services.AddHostedService<TimedHostedService>(); // veya builder.Services.AddHostedService<QueuedHostedService>(); var host = builder.Build(); await host.RunAsync(); // Uygulama çalışmaya başlar, Ctrl+C ile durdurulana kadar devam eder.
2. Modern Zamanlayıcı: PeriodicTimer (Uzun Süren İşlemler için)
Task.Delay ile periyodik işlem yapmak, her döngüde yeni bir Task oluşturur ve zamanlamada kaymalara (drift) neden olabilir. .NET 6 ile gelen PeriodicTimer, daha kararlı, düşük maliyetli ve drift'siz bir alternatiftir.
csharp
public class PeriodicTimerService : BackgroundService
{
private readonly ILogger<PeriodicTimerService> _logger;
public PeriodicTimerService(ILogger<PeriodicTimerService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("PeriodicTimer servisi başlıyor.");
// 5 saniyede bir çalışacak timer (başlangıçta hemen çalışmaz)
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
try
{
// WaitForNextTickAsync, bir sonraki tick'e kadar bekler.
// Timer durdurulursa false döner.
while (await timer.WaitForNextTickAsync(stoppingToken))
{
_logger.LogInformation("Tick çalıştı: {Time}", DateTimeOffset.Now);
// Burada uzun işlemler yapabilirsiniz (veritabanı, http çağrısı vb.)
// Ancak tick süresi işlem süresinden uzun olmalı, yoksa overlap olur.
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Servis durdurma sinyali aldı.");
}
_logger.LogInformation("Servis durdu.");
}
}
Avantajları:
-
Zamanlama kayması (drift) yoktur. Her 5 saniyede tam olarak tetiklenir (işlem süresi 5 saniyeden az olmalıdır).
-
usingile kaynak yönetimi otomatiktir. -
CancellationTokenile temiz durdurma sağlar.
⚠️ Kritik Uyarı: PeriodicTimer içinde yaptığınız işlem, timer aralığından uzun sürerse overlap yaşanır (yani bir önceki işlem bitmeden yeni tick gelir). Bu durumu önlemek için ya işlemi Task.Run ile arka plana atın ya da timer aralığını işlem süresine göre ayarlayın.
3. Worker Service Projesi ve Windows/Linux Servisine Dönüştürme
Visual Studio'dan "Worker Service" şablonu ile başladığınızda, zaten BackgroundService'ten türetilmiş bir sınıf ve IHost yapılanması gelir. Bu proje, konsol uygulaması gibi görünse de aslında bir Windows Servisi veya Linux systemd servisi olarak çalıştırılmaya hazırdır.
Windows Servisi Olarak Yayınlama:
csharp
// Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
// Windows servisi olarak çalışması için:
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "MyBackgroundService";
});
// veya Linux systemd için:
// builder.Services.AddSystemd();
var host = builder.Build();
await host.RunAsync();
Yayınlama ve Yükleme:
-
dotnet publish --configuration Release --output ./publish -
Windows'ta
sc create MyBackgroundService binPath= "C:\path\to\MyApp.exe"(veya PowerShell ile) -
Linux'ta
/etc/systemd/system/altına.servicedosyası oluşturupsystemctl start MyApp
4. Queue ve Channel ile Arka Plan İş Kuyruğu (Producer-Consumer)
Web API'den gelen istekleri arka planda işlemek istiyorsanız (ör. e-posta gönderme), BackgroundService ile bir kuyruk tüketicisi oluşturabilirsiniz.
csharp
// Kuyruk servisi (Singleton)
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue;
public BackgroundTaskQueue(int capacity = 100)
{
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, Task>>(options);
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null) throw new ArgumentNullException(nameof(workItem));
_queue.Writer.TryWrite(workItem);
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
{
return await _queue.Reader.ReadAsync(cancellationToken);
}
}
// Tüketici BackgroundService
public class QueuedHostedService : BackgroundService
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILogger<QueuedHostedService> logger)
{
_taskQueue = taskQueue;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await _taskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "İşlem sırasında hata oluştu.");
}
}
}
}
// Controller'da kullanım:
[ApiController]
[Route("api/[controller]")]
public class EmailController : ControllerBase
{
private readonly IBackgroundTaskQueue _taskQueue;
public EmailController(IBackgroundTaskQueue taskQueue)
{
_taskQueue = taskQueue;
}
[HttpPost]
public IActionResult SendEmail([FromBody] EmailRequest request)
{
_taskQueue.QueueBackgroundWorkItem(async (ct) =>
{
// Bu kod arka planda, isteğin dışında çalışır.
await SendEmailAsync(request, ct);
});
return Accepted(); // İstemciye hemen dön, iş arka planda yapılsın.
}
}
Bu model, web API'lerinde background processing için standarttır ve uygulamanızı tıkamaz.
5. Performans ve Dikkat Edilmesi Gerekenler
-
ExecuteAsynciçinde sonsuz döngü (while(true)) kullanmayın; her zamanstoppingTokenkontrolü yapın. -
Uzun süren işlemler için
Task.DelayveyaPeriodicTimerdışındaThread.SleepKULLANMAYIN – çünkü bu thread'i bloklar vestoppingTokeniptalini kontrol edemezsiniz. -
DI ile Scope Yönetimi:
BackgroundService'in kendisi geneldeSingleton'dır. İçindeScopedbir servis (ör.DbContext) kullanacaksanız, birIServiceScopeFactoryile her işlem için yeni bir scope oluşturun.
csharp
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// dbContext ile işlem yap
}
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
-
Exception Yönetimi:
ExecuteAsynciçinde fırlayan yakalanmamış exception, servisin çökmesine neden olur. Bu nedenle ana döngüyü birtry-catchile sarmalayın ve hatayı loglayıp devam edin (yeniden deneme mekanizması ekleyin).
Sonuç:
BackgroundService ve IHostedService, .NET Core ile arka plan işlemlerini profesyonelce yapmanızın olmazsa olmazıdır.
-
Basit periyodik işler için
PeriodicTimer+BackgroundService. -
İş kuyruğu (queue) için
Channel+BackgroundService. -
Windows/Linux servisi olarak çalıştırmak için
AddWindowsServiceveyaAddSystemd.
Unutmayın: Arka plan servisleri, uygulamanızın can damarıdır; hata yönetimini, loglamayı ve durdurma sinyallerini (graceful shutdown) asla ihmal etmeyin.