Modern yazılım geliştirme süreçlerinde en büyük problemlerden biri, uzun süren işlemleri doğru şekilde yönetmektir. Özellikle .NET Core ile geliştirilen web uygulamalarında, veri işleme, entegrasyon süreçleri ve arka plan görevleri doğrudan HTTP isteği içinde çalıştırıldığında ciddi performans sorunları ortaya çıkar.
Bu noktada BackgroundService, .NET Core ekosisteminde arka plan işlemlerini yönetmek için kullanılan en kritik yapılardan biridir. Ancak birçok geliştirici bu yapıyı ya yanlış kullanır ya da eksik anlar. Bu rehberde, sadece temel anlatımla kalmayacak; gerçek dünya senaryoları, performans detayları ve doğru mimari yaklaşımlarla konuyu derinlemesine ele alacağız.
Eğer ölçeklenebilir ve stabil bir backend sistemi kurmak istiyorsanız, bu konuyu doğru anlamanız şarttır.
BackgroundService Nedir?
BackgroundService, .NET Core içerisinde uzun süre çalışan işlemleri uygulamanın HTTP request akışından bağımsız olarak arka planda yürüten bir soyut sınıftır. Bu yapı sayesinde kullanıcı isteğine bağlı olmayan, sürekli veya gecikmeli çalışması gereken işlemler güvenli şekilde yönetilebilir.
Temel Amaç Nedir?
BackgroundService’in temel amacı, kullanıcı isteğine bağlı olmayan işlemleri sistemden izole ederek daha kontrollü ve sürdürülebilir bir şekilde çalıştırmaktır.
- İşlemleri request lifecycle’dan ayırır
- Bağımsız çalışan servisler oluşturur
- Uzun süreli görevleri yönetilebilir hale getirir
Nasıl Çalışır?
BackgroundService, .NET Core’un IHostedService altyapısı üzerine kuruludur ve uygulama başlatıldığında otomatik olarak devreye girer.
- Uygulama başlatılır
- Hosted Service sistemi ayağa kalkar
- BackgroundService tetiklenir
- ExecuteAsync metodu çalışır
public class MyWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("Arka plan işlemi çalışıyor...");
await Task.Delay(2000, stoppingToken);
}
}
}
Ne Tür İşler İçin Kullanılır?
BackgroundService, kullanıcıdan bağımsız çalışan süreçler için tasarlanmıştır:
- Mesaj kuyruğu tüketimi
- Veri senkronizasyon işlemleri
- E-posta ve bildirim gönderimi
- Dosya işleme süreçleri
- Periyodik kontrol işlemleri
Önemli Özellikleri
| Özellik | Açıklama |
|---|---|
| Lifecycle entegrasyonu | Uygulama ile birlikte başlar ve durur |
| Asenkron çalışma | Non-blocking işlem yapar |
| Bağımsız execution | HTTP request’e bağlı değildir |
| Uzun süreli çalışma | Servis sürekli aktif kalabilir |
Ne Değildir?
BackgroundService’in doğru anlaşılması için ne olmadığını bilmek kritik:
- Bir job scheduler değildir
- Tek başına queue sistemi değildir
- Cron yönetim aracı değildir
Bu tür ihtiyaçlar için genellikle ek sistemlerle birlikte kullanılır.
Kritik Mimari Not
BackgroundService bir altyapıdır, çözüm değildir. Gerçek dünyada çoğunlukla queue, retry ve logging mekanizmaları ile birlikte kullanılarak tam bir arka plan işlem sistemi oluşturulur.
.NET Core Hosted Service Mimarisi
Hosted Service mimarisi, .NET Core uygulamalarında arka plan servislerinin uygulama yaşam döngüsü ile entegre şekilde çalışmasını sağlayan altyapıdır. Bu mimari sayesinde uygulama başlatıldığında devreye giren ve uygulama kapanana kadar çalışan servisler oluşturulabilir.
Mimarinin Temel Bileşenleri
Hosted Service yapısı üç ana bileşenden oluşur:
- IHostedService → Temel kontrat (interface)
- BackgroundService → Kolaylaştırılmış abstract sınıf
- Generic Host → Tüm sistemi yöneten ana yapı
IHostedService Nedir?
IHostedService, arka plan servislerinin nasıl başlatılıp durdurulacağını tanımlayan temel interface’tir. Bu yapı, servis lifecycle’ını manuel olarak kontrol etmek isteyen geliştiriciler için düşük seviyeli bir çözüm sunar.
- StartAsync → Servis başlatılırken çalışır
- StopAsync → Servis durdurulurken çalışır
public class CustomService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Servis başladı");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Servis durdu");
return Task.CompletedTask;
}
}
BackgroundService Bu Yapıda Nerede Konumlanır?
BackgroundService, IHostedService’in daha pratik bir implementasyonudur. Geliştiriciye lifecycle yönetimi yerine doğrudan iş mantığına odaklanma imkanı verir.
Bu yapı sayesinde sadece ExecuteAsync metodunu yazmak yeterlidir:
public class MyWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(1000, stoppingToken);
}
}
}
Generic Host Nedir?
Generic Host, .NET Core uygulamasının çalışmasını sağlayan ana altyapıdır. Hosted Service’ler bu yapı tarafından yönetilir.
Generic Host şu sorumlulukları üstlenir:
- Dependency Injection yönetimi
- Configuration yükleme
- Logging altyapısı
- Hosted Service’leri başlatma ve durdurma
Servis Nasıl Register Edilir?
Bir BackgroundService’i sisteme dahil etmek için DI container’a eklenmesi gerekir:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<MyWorker>();
var app = builder.Build();
app.Run();
Bu işlemden sonra uygulama başlatıldığında servis otomatik olarak çalışır.
Lifecycle Entegrasyonu
Hosted Service mimarisi doğrudan uygulama lifecycle’ına bağlıdır. Bu entegrasyon sayesinde servisler manuel kontrol gerektirmeden yönetilir.
- Uygulama başlar
- Generic Host ayağa kalkar
- Tüm Hosted Service’ler başlatılır
- Servisler çalışmaya başlar
- Uygulama kapanırken servisler durdurulur
Dependency Injection ile Çalışma
Hosted Service’ler, .NET Core’un DI sistemi ile tam uyumludur. Bu sayede servisler içerisinde başka servisler kolayca kullanılabilir.
public class EmailWorker : BackgroundService
{
private readonly IEmailService _emailService;
public EmailWorker(IEmailService emailService)
{
_emailService = emailService;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _emailService.SendAsync();
}
}
Bu yapı modüler, test edilebilir ve sürdürülebilir kod yazmayı sağlar.
Mimari Avantaj
Hosted Service mimarisi, arka plan işlemlerini uygulamaya entegre ederken kontrolü tamamen sistem seviyesinde tutar. Bu sayede servisler güvenli, düzenli ve öngörülebilir şekilde çalışır.
Neden BackgroundService Kullanılmalı?
BackgroundService, uzun süren veya sürekli çalışan işlemleri HTTP request sürecinden ayırarak sistemin daha stabil ve verimli çalışmasını sağlar. Bu yaklaşım, özellikle yüksek trafikli uygulamalarda performans problemlerini önlemek için kritik bir rol oynar.
1. HTTP Request Süresini Kısaltır
Uzun süren işlemler doğrudan request içinde çalıştırıldığında kullanıcı beklemek zorunda kalır. BackgroundService ile bu işlemler arka plana alınır ve request hızlıca tamamlanır.
- Kullanıcı beklemez
- Response süresi düşer
- Uygulama daha hızlı hissedilir
2. Thread Bloklanmasını Önler
ASP.NET Core her request için thread kullanır. Uzun işlemler bu thread’leri meşgul eder. BackgroundService kullanımı ile thread’ler serbest kalır.
| Yaklaşım | Sonuç |
|---|---|
| Controller içinde uzun işlem | Thread bloklanır |
| BackgroundService kullanımı | Thread serbest kalır |
3. Timeout Problemlerini Engeller
Web uygulamalarında request süresi sınırlıdır. Uzun işlemler timeout hatasına neden olabilir. BackgroundService ile bu risk ortadan kaldırılır.
- Timeout hataları azalır
- İşlemler kesilmez
- Sistem daha güvenilir çalışır
4. Uzun Süreli İşlemleri Ayrıştırır
Uzun süren işlemleri ayrı bir katmana almak, sistemin daha düzenli çalışmasını sağlar.
- İş mantığı ayrılır
- Kod daha temiz olur
- Bakım kolaylaşır
5. Sürekli Çalışan İşleri Destekler
Bazı işlemler uygulama boyunca sürekli çalışmalıdır. BackgroundService bu tür senaryolar için doğal bir çözümdür.
- Queue dinleme
- Sistem kontrolü
- Veri senkronizasyonu
6. Hata İzolasyonu Sağlar
Arka plan işlemleri request akışından bağımsız olduğu için oluşan hatalar doğrudan kullanıcıyı etkilemez.
- Hatalar izole edilir
- Sistem çökmez
- Kontrollü hata yönetimi yapılır
Worker ve Queue Mimarisi
Worker ve Queue mimarisi, arka plan işlemlerinin kontrollü ve sıralı şekilde işlenmesini sağlayan bir sistem tasarımıdır. Bu yapıda gelen işler doğrudan çalıştırılmaz; önce bir kuyruğa alınır, ardından worker servisleri tarafından işlenir.
Mimari Akış
- İstek sisteme gelir
- İş kuyruğa eklenir
- Worker kuyruğu dinler
- İş alınır ve işlenir
- Sonuç kaydedilir
Client → API → Queue → Worker → Database
Queue (Kuyruk) Yapısı
Queue, işlerin belirli bir sıraya göre işlendiği veri yapısıdır. Genellikle FIFO (First In First Out) mantığı ile çalışır.
- İşleri sıraya alır
- Yoğunluğu dengeler
- Asenkron çalışma sağlar
Basit Queue Örneği
public class JobQueue
{
private readonly Queue<string> _jobs = new();
public void Enqueue(string job)
{
_jobs.Enqueue(job);
}
public string Dequeue()
{
return _jobs.Count > 0 ? _jobs.Dequeue() : null;
}
}
Worker (İşleyici) Yapısı
Worker, kuyruğa alınan işleri arka planda işleyen servistir. Genellikle BackgroundService kullanılarak oluşturulur.
public class QueueWorker : BackgroundService
{
private readonly JobQueue _queue;
public QueueWorker(JobQueue queue)
{
_queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var job = _queue.Dequeue();
if (job != null)
{
Console.WriteLine($"İşleniyor: {job}");
}
await Task.Delay(500, stoppingToken);
}
}
}
Çalışma Mantığı
- Worker sürekli kuyruğu kontrol eder
- Yeni iş geldiğinde işlem başlatır
- İşler sırayla veya kontrollü paralel şekilde yürütülür
Queue Türleri
| Tür | Açıklama |
|---|---|
| In-Memory Queue | Basit ve tek sunuculu sistemler için |
| Redis Queue | Hızlı ve dağıtık sistemler için |
| RabbitMQ | Mesajlaşma odaklı sistemler için |
| Kafka | Yüksek hacimli veri akışı için |
İşleme Modelleri
- Single Consumer → Tek worker işler
- Multiple Consumer → Birden fazla worker paralel çalışır
Temel Prensip
İş üretimi ile iş işleme birbirinden ayrılmalıdır.
Bu ayrım sayesinde sistem daha kontrol edilebilir ve genişletilebilir hale gelir.
Yaygın Hatalar
- Queue olmadan worker kullanmak
- İşleri doğrudan çalıştırmak
- Kuyruk yönetimini ihmal etmek
Concurrency ve Performans Yönetimi
Concurrency (eşzamanlılık) yönetimi, aynı anda çalışan işlem sayısını kontrol ederek sistem kaynaklarının dengeli kullanılmasını sağlar. Performans yönetimi ise bu işlemlerin en verimli şekilde yürütülmesini hedefler.
Concurrency Nedir?
Concurrency, birden fazla işlemin aynı anda yürütülmesidir. Worker tabanlı sistemlerde bu, aynı anda birden fazla job’ın işlenmesi anlamına gelir.
Neden Kontrol Edilmelidir?
- CPU aşırı kullanımı oluşabilir
- Bellek tüketimi artar
- Dış servisler rate limit uygulayabilir
- Sistem stabilitesi bozulabilir
CPU-bound ve IO-bound Ayrımı
| İş Türü | Açıklama | Yaklaşım |
|---|---|---|
| CPU-bound | Yoğun hesaplama işlemleri | Düşük paralellik |
| IO-bound | API, DB, dosya işlemleri | Daha yüksek paralellik |
Semaphore ile Limit Koyma
Concurrency kontrolü için en yaygın yöntem SemaphoreSlim kullanmaktır.
private readonly SemaphoreSlim _semaphore = new(3);
public async Task ProcessAsync()
{
await _semaphore.WaitAsync();
try
{
await DoWork();
}
finally
{
_semaphore.Release();
}
}
Bu yapı aynı anda maksimum 3 işlemin çalışmasını sağlar.
Paralel İşlem Yönetimi
var tasks = jobs.Select(job => ProcessJob(job));
await Task.WhenAll(tasks);
Kontrolsüz kullanıldığında sistem aşırı yüklenebilir, bu nedenle sınırlandırma gereklidir.
Batch Processing
Büyük iş listeleri parçalara bölünerek işlenmelidir.
var batches = jobs.Chunk(10);
foreach (var batch in batches)
{
var tasks = batch.Select(x => ProcessJob(x));
await Task.WhenAll(tasks);
}
Rate Limiting
Dış servislerle çalışırken istekler sınırlandırılmalıdır.
await Task.Delay(200);
- API limit aşımı önlenir
- Sistem dengeli çalışır
Backpressure
Sisteme gelen iş miktarını kontrol etmek gerekir.
- Queue uzunluğu sınırlandırılır
- Yeni işler reddedilebilir
- Worker sayısı ayarlanabilir
Temel Kurallar
- Sınırsız paralellikten kaçın
- Async/await kullan
- Kaynak kullanımını gözlemle
- İş tipine göre strateji belirle
Retry ve Hata Yönetimi
Retry ve hata yönetimi, arka plan işlemlerinin başarısız olduğu durumlarda sistemin stabil kalmasını ve işlemlerin güvenli şekilde yeniden denenmesini sağlar.
Retry Nedir?
Retry, başarısız bir işlemin tekrar çalıştırılmasıdır. Özellikle geçici hatalarda kritik rol oynar.
Hangi Durumlarda Kullanılır?
- API timeout
- Ağ bağlantı sorunları
- Geçici veritabanı hataları
- Rate limit problemleri
Basit Retry Örneği
int maxRetry = 3;
for (int i = 0; i < maxRetry; i++)
{
try
{
await DoWork();
break;
}
catch
{
await Task.Delay(1000);
}
}
Exponential Backoff
Her retry denemesinde bekleme süresi artırılır.
for (int i = 0; i < 3; i++)
{
try
{
await DoWork();
break;
}
catch
{
int delay = (int)Math.Pow(2, i) * 1000;
await Task.Delay(delay);
}
}
Retry Edilmemesi Gereken Hatalar
| Hata Türü | Retry |
|---|---|
| Network hatası | Evet |
| Timeout | Evet |
| Validation hatası | Hayır |
| Null reference | Hayır |
Exception Handling
try
{
await ProcessJob();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Hatalar mutlaka yakalanmalı ve kontrol altına alınmalıdır.
Idempotency
Aynı işlem tekrar çalıştırıldığında sonuç değişmemelidir.
if (IsProcessed(jobId))
{
return;
}
Dead Letter Queue
Retry sonrası başarısız olan işler ayrı bir kuyruğa alınır.
- Başarısız işler kaybolmaz
- Sonradan incelenebilir
Polly ile Gelişmiş Retry
var policy = Policy
.Handle()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
await policy.ExecuteAsync(async () =>
{
await DoWork();
});
Temel Kurallar
- Sınırsız retry yapma
- Sadece geçici hataları retry et
- Bekleme süresi kullan
- Hataları logla
Gerçek Kod Örnekleri
BackgroundService ve worker mimarisini doğru anlamanın en etkili yolu, gerçek senaryolara yakın kod örnekleri incelemektir. Aşağıdaki örnekler, arka plan işlem sistemlerinin nasıl kurulduğunu pratik olarak gösterir.
1. Basit Worker
public class SimpleWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("Worker çalışıyor...");
await Task.Delay(2000, stoppingToken);
}
}
}
2. Queue Tabanlı Worker
public class JobQueue
{
private readonly Queue _jobs = new();
public void Enqueue(string job)
{
_jobs.Enqueue(job);
}
public string Dequeue()
{
return _jobs.Count > 0 ? _jobs.Dequeue() : null;
}
}
public class QueueWorker : BackgroundService
{
private readonly JobQueue _queue;
public QueueWorker(JobQueue queue)
{
_queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var job = _queue.Dequeue();
if (job != null)
{
Console.WriteLine($"İşleniyor: {job}");
}
await Task.Delay(500, stoppingToken);
}
}
}
3. Zamanlanmış Worker
public class ScheduledWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("Görev çalıştı");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
4. Paralel İşleme
public async Task ProcessJobsAsync(List jobs)
{
var tasks = jobs.Select(job => ProcessJob(job));
await Task.WhenAll(tasks);
}
5. Limitli Paralellik
private readonly SemaphoreSlim _semaphore = new(3);
public async Task ProcessAsync(string job)
{
await _semaphore.WaitAsync();
try
{
await Task.Delay(1000);
}
finally
{
_semaphore.Release();
}
}
6. Retry Mekanizması
public async Task ProcessWithRetry()
{
for (int i = 0; i < 3; i++)
{
try
{
await DoWork();
break;
}
catch
{
await Task.Delay(1000);
}
}
}
7. Dependency Injection Kullanımı
public class EmailWorker : BackgroundService
{
private readonly IEmailService _service;
public EmailWorker(IEmailService service)
{
_service = service;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _service.SendAsync();
}
}
8. Logging
private readonly ILogger _logger;
_logger.LogInformation("Worker başladı");
Örnek Akış
API → Queue → Worker → Database
Önemli Notlar
- Async/await kullanılmalıdır
- CancellationToken ihmal edilmemelidir
- Retry mekanizması eklenmelidir
- Concurrency kontrolü yapılmalıdır
Alternatif Çözümler
BackgroundService, arka plan işlemleri için güçlü bir yapı sunar; ancak bazı senaryolarda farklı araçlar daha uygun olabilir. Özellikle zamanlama, job yönetimi ve dağıtık sistem ihtiyaçlarında alternatif çözümler tercih edilir.
1. Hangfire
- Dashboard ile job takibi
- Hazır retry mekanizması
- Cron destekli zamanlama
BackgroundJob.Enqueue(() => Console.WriteLine("Job çalıştı"));
2. Quartz.NET
- Zaman bazlı job scheduling
- Cron ifadeleri ile çalışma
- Detaylı zamanlama kontrolü
IJobDetail job = JobBuilder.Create().Build();
3. Azure Functions
- Serverless yapı
- Otomatik ölçeklenme
- Event-driven çalışma
public static void Run(TimerInfo myTimer, ILogger log)
{
log.LogInformation("Çalıştı");
}
4. Message Queue Sistemleri
- RabbitMQ
- Kafka
- Redis Queue
Producer → Queue → Consumer
5. OS Seviyesi Servisler
- Windows Service
- Linux Daemon
Karşılaştırma
| Çözüm | Kullanım | Avantaj |
|---|---|---|
| BackgroundService | Genel worker | Basit ve entegre |
| Hangfire | Job yönetimi | Dashboard |
| Quartz.NET | Zamanlama | Cron desteği |
| Azure Functions | Serverless | Otomatik scaling |
| RabbitMQ/Kafka | Dağıtık sistem | Yüksek performans |
Sık Sorulan Sorular
BackgroundService memory leak yapar mı?
Doğrudan yapmaz; ancak yanlış resource yönetimi, sürekli büyüyen koleksiyonlar veya dispose edilmeyen nesneler memory leak’e neden olabilir.
Worker crash olursa ne olur?
Unhandled exception oluşursa worker durabilir. Bu nedenle exception handling ve gerekiyorsa restart stratejisi uygulanmalıdır.
Scoped servisler neden doğrudan kullanılamaz?
BackgroundService singleton olarak çalışır. Scoped servisler ise request bazlıdır. Bu nedenle CreateScope kullanılarak oluşturulmalıdır.
BackgroundService içinde DbContext kullanılır mı?
Evet, ancak doğrudan değil. Her işlem için yeni bir scope oluşturularak kullanılmalıdır.
Worker kaç adet çalıştırılmalı?
Bu, sistem kaynaklarına ve iş yüküne bağlıdır. Genellikle concurrency limiti belirlenerek kontrollü şekilde artırılır.
BackgroundService ne zaman durur?
Uygulama kapanırken veya CancellationToken tetiklendiğinde durur.
Queue olmadan kullanılabilir mi?
Evet, ancak karmaşık ve yoğun sistemlerde önerilmez.
Retry kaç kez yapılmalı?
Genellikle 3–5 deneme yeterlidir. Daha fazlası sistem yükünü artırabilir.
Logging zorunlu mudur?
Evet. Arka plan işlemlerinde hata takibi ve izleme için logging kritik öneme sahiptir.
BackgroundService ile cron job yapılabilir mi?
Evet, ancak gelişmiş zamanlama ihtiyaçları için Quartz.NET veya Hangfire tercih edilmelidir.


Yorum Yap