---
title: IHostedService mi, BackgroundService mi, Hangfire mı? .NET'te Arka Plan İşleri
description: Bir .NET uygulamasında e-posta göndermekten toplu veri işlemeye kadar pek çok işi kullanıcı isteğinden bağımsız yürütebiliriz. Peki bunun için IHostedService mi, BackgroundService mi, yoksa Hangfire mı kullanmalıyız? Bu yazıda arka plan işleri ile zamanlanmış görevler arasındaki ayrımı açıklıyor, bu üç yaklaşımın nasıl çalıştığını kod örnekleriyle inceliyorum. Son olarak hangi senaryoda hangisini tercih edebileceğimizi ve uygulamada dikkat etmemiz gereken noktaları ele alıyorum.
author: Burak Kaşıkcı
date: 2026-10-11
updated: 2026-10-11
tags:
  - .net
  - "c#"
  - hangfire
  - backgroundservice
  - ihostedservice
  - background jobs
  - asp.net core
  - yazılım mimarisi
canonical: https://burakkasikci.com/blog/ihostedservice-mi-backgroundservice-mi-hangfire-mi-nette-arka-plan-isleri
slug: ihostedservice-mi-backgroundservice-mi-hangfire-mi-nette-arka-plan-isleri
---

Bir API geliştirdiğimizi düşünelim. Kullanıcı bir dosya yüklüyor ve bu dosyada binlerce e-posta adresi bulunuyor. Akışımız gereği de her adresi doğrulayıp sonuçları veritabanına yazmamız ve sonrasında da kullanıcıya bunları doğru şekilde raporlamamız gerekiyor. (Yakın zamanda geliştirmeye başladığım [SMTPSeer.com](https://smtpseer.com) dan bir ipucu vermiş olayım :)) )
Bütün bu akışları aynı HTTP isteği içerisinde tamamlamaya çalışmak ne kadar mantıklı? İşlem uzadıkça kullanıcının bekleme süresi artacak, istek zaman aşımına uğrayabilecek ve kaynaklarımız gereksiz yere meşgul olabilecek, bu senaryoda ne kullanıcı mutlu olur, ne de sistem doğru çalışır.

Böyle durumlarda işi arka plana bırakmak çok daha mantıklı bir seçenek. Kullanıcıya işlemin başlatıldığını bildirir, doğrulama sürecini ayrı yürütür ve sonuçları daha sonra sunabiliriz.
Ancak burada başka bir soru ortaya çıkıyor: *Bu işi nasıl çalıştıracağız?*

.NET tarafında `IHostedService`, `BackgroundService` ve `Hangfire` gibi seçeneklerimiz var. İlk bakışta hepsi aynı amaca hizmet ediyorlarmış gibi görünebilirler. Açıkçası ben de bu kavramları değerlendirirken önce hangi problemin çözülmesi gerektiğini netleştirmenin önemli olduğunu düşünüyorum. Çünkü bunlar birbirinin doğrudan alternatifi değil; farklı ihtiyaçlara cevap veriyorlar.

Önce temel kavramları ayıralım, ardından kod tarafına geçelim.

## Background Job Nedir, Ne İşe Yarar?

Background job, kullanıcının o anki isteğinden bağımsız yürütülen bir işi ifade eder. İşlem arka planda devam ederken uygulama kullanıcıya yanıt verebilir veya başka istekleri karşılamayı sürdürebilir.
Günlük geliştirme çalışmalarında bunun pek çok örneğiyle karşılaşabiliriz:

* Kullanıcı kayıt olduktan sonra hoş geldin e-postası göndermek.
* Büyük CSV dosyalarını satır satır işlemek.
* Günlük veya haftalık raporlar hazırlamak.
* Süresi dolan kayıtları temizlemek.
* Harici servislerden veri çekmek.
* Başarısız işlemleri belirli kurallara göre yeniden denemek.

Bu işlerin ortak noktası, hepsinin aynı anda veya aynı yöntemle yürütülmek zorunda olmaması.

Örneğin kullanıcı kayıt olduğunda e-posta göndermek için işi kuyruğa alabiliriz. Günlük rapor içinse belirli bir saati beklememiz gerekebilir. Bir başka senaryoda, sisteme gelen yüzlerce işi sırayla işleyip kaynak kullanımını kontrol altında tutmak isteyebiliriz.

Burada üç kavramı birbirinden ayırmak gerekiyor:

* **Arka plan işi:** İşin nasıl bir çalışma bağlamında yürütüldüğüyle ilgilidir.
* **Zamanlanmış iş:** İşin ne zaman veya hangi aralıklarla çalışacağını belirler.
* **Kuyruk yönetimi:** İşlerin sıraya alınması, uygun kaynaklarla yürütülmesi ve durumlarının izlenmesiyle ilgilidir.

Bir iş aynı zamanda hem arka planda çalışan hem de zamanlanmış bir iş olabilir. Ancak arka planda çalışıyor olması, kendiliğinden zamanlama veya kalıcı kuyruk yönetimi özelliklerine sahip olduğu anlamına gelmez.
Bu ayrım, birazdan ele alacağımız araçları doğru değerlendirmek için önemli.

## IHostedService: Servis Yaşam Döngüsünü Yönetmek

`IHostedService`, .NET Generic Host tarafından yönetilen servisler için bir yaşam döngüsü sözleşmesi sunar.
İki temel metodu vardır:

* `StartAsync`: Host başlatılırken çağrılır.
* `StopAsync`: Host kontrollü şekilde kapatılırken çağrılır.

Örneğin uygulama başladığında bir başlangıç kontrolü yapmak veya uygulama durdurulurken özel bir kapatma işlemi gerçekleştirmek isteyebiliriz.

```
public sealed class StartupService : IHostedService
{
    public Task StartAsync(
        CancellationToken cancellationToken)
    {
        Console.WriteLine("Servis başlatıldı.");

        return Task.CompletedTask;
    }

    public Task StopAsync(
        CancellationToken cancellationToken)
    {
        Console.WriteLine("Servis durduruluyor.");

        return Task.CompletedTask;
    }
}
```

Kaydı da oldukça basit:

```
builder.Services.AddHostedService<StartupService>();
```

Burada dikkat edilmesi gereken nokta, `IHostedService` arayüzünün bize hazır bir iş kuyruğu veya zamanlayıcı sunmaması. Servisin ne zaman başlatılıp durdurulacağını tanımlar; asıl davranışı bizim uygulamamız gerekir.
Örneğin `StartAsync` içerisinde uzun süren bir işlem çalıştırırsak uygulamanın başlangıcını geciktirebiliriz. Dolayısıyla bu metodu sürekli çalışan bir işin tamamını yürütmek için kullanmak her zaman doğru tercih olmayabilir.
Peki uygulama boyunca çalışacak bir kontrol döngüsüne ihtiyacımız varsa?
İşte o zaman `BackgroundService` daha kullanışlı hâle geliyor.

## BackgroundService: Sürekli Çalışan İşler İçin

`BackgroundService`, `IHostedService` arayüzünü uygulayan soyut bir temel sınıftır. Uzun süre çalışan asenkron işlemleri yazarken yaşam döngüsü kodunun önemli bir bölümünü bizim yerimize yönetir.
Örneğin her dakika kontrol edilmesi gereken kayıtlarımız olsun. Bu kontrolü şu şekilde yazabiliriz:

```
public sealed class CleanupService : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine("Temizlik kontrolü yapılıyor.");

            await Task.Delay(
                TimeSpan.FromMinutes(1),
                stoppingToken);
        }
    }
}
```

Ardından servisi kaydedelim:

```
builder.Services.AddHostedService<CleanupService>();
```

`ExecuteAsync`, arka plan çalışmasının ana noktasıdır. Host servisi başlattığında bu metot devreye girer ve iptal sinyali gelene kadar döngümüz devam eder.
Burada küçük ama önemli bir ayrıntı var: `Task.Delay` kullanarak beklemek, bir işi üretim ortamına uygun bir zamanlayıcıya dönüştürmez. **Uygulama yeniden başlatılırsa bekleme sıfırlanabilir; işlem uzun sürerse döngünün davranışını ayrıca tasarlamamız gerekir.**

Yani basit bir periyodik kontrol için bu yaklaşım gayet yeterli olabilir. Fakat zamanlamanın kalıcı olması, çalıştırmaların izlenmesi veya başarısız işlerin yeniden denenmesi gerekiyorsa ek mekanizmalara ihtiyaç duyarız.

### IHostedService ile BackgroundService arasındaki fark


İkisini karşılaştırırken aklımızda şu ayrım kalabilir:

| Özellik | IHostedService | BackgroundService |
| ------- | -------------- | ----------------- |
| Yapısı | Arayüz | Soyut temel sınıf |
| Yaşam döngüsü | Başlatma ve durdurma sözleşmesini tanımlar | Bu sözleşmeyi temel sınıf üzerinden uygular |
| Uzun süreli asenkron çalışma | Kendimiz yönetiriz | `ExecuteAsync` ile kolaylaşır |
| Hazır iş kuyruğu | Yok | Yok |
| Yerleşik retry | Yok | Yok |
| Zamanlama | Ek kod veya araç gerekir | Ek kod veya araç gerekir |

Buradan çıkaracağımız sonuç basit: **Her `BackgroundService`, `IHostedService` sözleşmesini uygular; ancak her `IHostedService`, `BackgroundService` olmak zorunda değildir.**
Ben olsam uygulama boyunca çalışacak bir döngü yazarken önce `BackgroundService` seçeneğini değerlendirirdim. Daha özel bir başlatma ve durdurma davranışı gerekiyorsa doğrudan `IHostedService` uygulamak da gayet mantıklı.

## Zamanlanmış İş ile Background Job Aynı Şey mi?

Bu iki kavramın sıkça birbirine karıştırıldığını görüyoruz.
*Background job, işin arka planda yürütülmesiyle ilgilidir. Zamanlanmış iş ise çalıştırma zamanının belirlenmesiyle.*
Örneğin:

* Kullanıcı kayıt olduğunda e-posta göndermek bir background job olabilir.
* Her gece saat 02.00'de rapor oluşturmak zamanlanmış bir background job olabilir.
* Her beş dakikada bir bekleyen kayıtları kontrol etmek periyodik bir görev olabilir.

Bunların hepsini `BackgroundService` ile gerçekleştirebiliriz. Ancak zamanlamayı, hata yönetimini ve uygulama yeniden başladığındaki davranışı kendimiz ele almamız gerekir.
İş sayısı azsa bu yaklaşımda bir sorun yok. Hatta yalnızca birkaç satır kodla çözülebilecek bir ihtiyaç için yeni bir altyapı eklemek gereksiz olabilir.
Fakat işler çoğaldığında aynı sorular tekrar karşımıza çıkar:

* İşin çalışacağı zamanı nerede saklayacağız?
* Uygulama yeniden başladığında planlanan işler ne olacak?
* İş başarısız olursa nasıl yeniden denenecek?
* Çalışan ve tamamlanan işleri nereden göreceğiz?
* Aynı işin birden fazla kez çalışmasını nasıl yöneteceğiz?

Bu soruların hepsini kendi kodumuzla çözebiliriz. Yalnızca bunun geliştirme ve bakım maliyetini de hesaba katmalıyız.
Hangfire'ın sağladığı kolaylık tam olarak burada ortaya çıkıyor.

## Hangfire: İşleri Kuyruğa Almak ve Yönetmek

***Hangfire****, .NET uygulamalarında arka plan işlerini oluşturmak, kuyruğa almak, çalıştırmak ve takip etmek için kullanılan bir kütüphanedir.*
Bence Hangfire'ı yalnızca bir zamanlayıcı olarak düşünmek eksik kalır. Asıl faydası, arka plan işlerinin yaşam döngüsünü yönetmek için ihtiyaç duyduğumuz birçok özelliği hazır sunmasıdır.
Bunlar arasında şunlar bulunur:

* İşlerin kalıcı bir storage üzerinde saklanması.
* Kuyruğa alınan işlerin worker'lar tarafından yürütülmesi.
* Gecikmeli ve tekrarlayan görevler oluşturulması.
* Hata alan işler için yeniden deneme mekanizması.
* İş durumlarının Dashboard üzerinden izlenmesi.

Örneğin bir toplu e-posta doğrulama uygulaması geliştirdiğimizi düşünelim. Kullanıcı bir dosya yüklediğinde her adresi aynı HTTP isteği içerisinde kontrol etmek yerine doğrulama işini kuyruğa alabiliriz.
API, işin tamamlanmasını beklemek yerine işlemin başlatıldığını bildirebilir. Hangfire Server ise kuyruğa alınan işi uygun worker üzerinden çalıştırır.
Burada önemli bir ayrıntı var: Hangfire kullanmak, işlerin otomatik olarak sınırsız kapasiteyle çalışacağı veya her koşulda tam bir kez yürütüleceği anlamına gelmez. Worker sayısı, storage, kaynak kapasitesi ve işin tasarımı hâlâ bizim sorumluluğumuzda.

### Hangfire nasıl çalışıyor?


Temel akışı dört parçaya ayırabiliriz.

1. **Job:** Gerçekleştirilmesini istediğimiz iş. Örneğin rapor oluşturmak veya e-posta göndermek.
2. **Storage:** Job bilgilerini ve durumlarını saklayan katman. SQL Server gibi desteklenen bir storage kullanılabilir.
3. **Hangfire Server:** Kuyrukları takip eder ve işleri worker'lar üzerinden yürütür. Bu bileşenin çalışır durumda olması gerekir.
4. **Dashboard:** Bekleyen, çalışan, tamamlanan ve başarısız olan işleri görmemizi sağlayan yönetim arayüzüdür.


Dashboard özellikle bir işin neden tamamlanmadığını araştırırken kullanışlıdır. Ancak üretim ortamında erişim yetkilendirmesini ihmal etmemeliyiz.

## Hangfire ile Üç Farklı İş Türü

Hangfire'ın kullanım biçimlerini anlamanın en kolay yolu, küçük örneklerle ilerlemek.

### 1\. Enqueue: İşi kuyruğa almak

Bir işin mümkün olan en kısa sürede arka planda çalışmasını istiyorsak `Enqueue` kullanabiliriz.

```
BackgroundJob.Enqueue<EmailService>(
    service => service.SendAsync(
        "user@example.com"));
```

Burada e-posta gönderimi, çağrının yapıldığı noktada tamamlanmış olmaz. İş kuyruğa alınır ve uygun bir worker tarafından yürütülür.
Kullanıcı kayıt olduktan sonra hoş geldin e-postası göndermek buna örnek olabilir.

### 2\. Schedule: İşi daha sonra çalıştırmak

Bir görevin belirli bir süre sonra yürütülmesini istiyorsak `Schedule` kullanabiliriz.

```
BackgroundJob.Schedule<EmailService>(
    service => service.SendReminderAsync(
        "user@example.com"),
    TimeSpan.FromMinutes(30));
```

Örneğin kullanıcı bir işlemi tamamlamadıysa 30 dakika sonra hatırlatma e-postası göndermek isteyebiliriz.
Burada verilen süre, işin o anda kesin olarak çalışacağı anlamına gelmez. İşin yürütülmesi, Hangfire Server'ın ve storage altyapısının durumuna bağlıdır.

### 3\. RecurringJob: İşi belirli aralıklarla tekrarlamak

Her gece rapor üretmek veya düzenli olarak temizlik yapmak istiyorsak tekrarlayan işlerden yararlanabiliriz.

```
RecurringJob.AddOrUpdate<ReportService>(
    "daily-report",
    service => service.GenerateAsync(),
    Cron.Daily(2));
```

Bu örnekte raporun her gün saat 02.00'de çalışması planlanır.
Recurring job tanımının yapılmış olması tek başına yeterli değildir. Planlanan işleri işleyecek Hangfire Server çalışır durumda olmalıdır. Ayrıca kullanılan Hangfire sürümüne göre API ayrıntıları değişebileceğinden, kodu projenin kullandığı sürümün dokümantasyonuna göre uyarlamak gerekir.

## Hangfire mı, BackgroundService mi? Karar Nasıl Verilir?

Geldik asıl soruya. "*Hangfire daha fazla özellik sunuyor diye her arka plan işinde onu kullanmalı mıyız?"*
Açıkçası ben bu yaklaşımı doğru bulmuyorum. Her projenin ihtiyaçları farklı. Küçük bir kontrol döngüsü için Hangfire kurmak, storage yapılandırmak ve ek operasyonel sorumluluklar almak gereksiz olabilir.
Öte yandan işlerin durumunu takip etmek, hataları yönetmek ve planlanmış görevleri uygulamanın yeniden başlamasından bağımsız biçimde saklamak istiyorsak Hangfire'ın sunduğu özellikler ciddi kolaylık sağlayabilir.

| İhtiyaç | Değerlendirilebilecek yaklaşım |
| ------- | ------------------------------ |
| Uygulama başlangıcında özel bir işlem yapmak | IHostedService |
| Uygulama boyunca asenkron bir döngü yürütmek | BackgroundService |
| Basit, periyodik bir kontrol gerçekleştirmek | BackgroundService |
| İşleri kuyruğa alıp durumlarını takip etmek | Hangfire |
| Gecikmeli veya tekrarlayan görevleri yönetmek | Hangfire |
| Başarısız işler için yapılandırılabilir retry kullanmak | Hangfire |

Bunlar kesin sınırlar değil. Örneğin `BackgroundService` ile kendi kuyruğumuzu, zamanlayıcımızı ve retry mekanizmamızı yazabiliriz. Ancak bunu yaptığımızda hazır bir altyapının sunduğu özellikleri kendimiz geliştirmiş oluruz.

Karar verirken şu soruları sorardım:

1. Uygulama yeniden başlatıldığında bekleyen işlerin korunması gerekiyor mu?
2. Başarısız işleri takip etmek ve yeniden denemek istiyor muyuz?
3. İşlerin durumunu bir yönetim ekranından incelemek gerekli mi?
4. Zamanlanmış görevlerin yönetimi karmaşıklaşıyor mu?
5. Yeni bir altyapının getireceği bakım ve operasyon maliyeti, sağlayacağı faydaya değiyor mu?

Bu soruların cevapları, hangi yaklaşımın daha uygun olduğunu gösterir.

## Hangfire Kullanırken Gözden Kaçırmamamız Gerekenler

Hangfire pek çok işi kolaylaştırsa da uygulama tarafındaki sorumluluklarımız ortadan kalkmıyor.

### Aynı iş birden fazla kez çalışabilir


Bir job'ın kuyruğa alınması, ilgili kodun yalnızca bir kez çalışacağını garanti etmez. Hatalar, yeniden denemeler veya kesintiler nedeniyle aynı mantıksal işlem birden fazla kez yürütülebilir.
Örneğin ödeme işlemini ele alalım. Harici servis ödemeyi tamamlamış olabilir ancak uygulama yanıtı alamadan hata yaşayabilir. Job yeniden çalıştığında aynı ödemenin ikinci kez alınmasını engellememiz gerekir.
Burada **idempotency** kavramı önem kazanıyor. Aynı mantıksal işlem tekrar yürütüldüğünde mükerrer veya istenmeyen sonuçlar oluşmamasını sağlamalıyız.

### Job parametrelerini küçük tutmak faydalı olabilir


Bir job oluştururken büyük nesneleri veya tüm veritabanı entity'lerini parametre olarak taşımak yerine çoğu zaman ilgili kaydın kimliğini göndermek daha yönetilebilir bir yaklaşım olur.

```
BackgroundJob.Enqueue<ReportService>(
    service => service.GenerateAsync(reportId));
```

Bu sayede job çalıştığında gerekli verileri güncel durumlarıyla veritabanından okuyabiliriz. Böylece büyük nesnelerin serileştirilmesi ve eski verilerin işlenmesi gibi riskleri azaltabiliriz.

### Retry her hatanın çözümü değil


Hangfire'ın yeniden deneme mekanizması bulunması, her hatanın otomatik olarak düzeleceği anlamına gelmez.
Geçici bir ağ hatası ile hatalı bir kullanıcı girdisi aynı şekilde ele alınmamalıdır. *Yeniden deneme sayısını ve stratejisini işin niteliğine göre belirlemek gerekir.* Sürekli başarısız olan işlerin izlenmesi ve gerektiğinde müdahale edilmesi de önemlidir.

### Storage ve worker kapasitesini planlamalıyız


Hangfire'ın kalıcı iş yönetimi özelliklerinden yararlanmak için uygun bir storage yapılandırması gerekir. Ayrıca işleri yürütecek Hangfire Server çalışır durumda olmalıdır.
İş yükü arttığında worker sayısı, kuyruklar, veritabanı bağlantıları ve harici servislerin limitleri de önem kazanır. Kuyruğa daha fazla iş eklemek, sistemin bu işleri aynı hızda tamamlayabileceği anlamına gelmez.

### BackgroundService içinde kaynak yönetimini unutmamalıyız


`BackgroundService` kullandığımızda özellikle veritabanı işlemlerinde servis yaşam döngülerine dikkat etmeliyiz.
Örneğin EF Core `DbContext` gibi scoped bir servisi singleton yaşam döngüsüne sahip bir arka plan servisine doğrudan enjekte etmek uygun değildir. Gerekli durumlarda `IServiceScopeFactory` ile yeni bir scope oluşturup bağımlılıkları bu scope üzerinden çözümleyebiliriz.
İptal token'ını doğru kullanmak, hataları loglamak ve uzun süren işlemlerin kontrollü şekilde durmasını sağlamak da aynı derecede önemli.

## Sonuç

.NET'te arka plan işleri için tek bir doğru yaklaşım yok. `IHostedService`, servis yaşam döngüsünü yönetmek için bir sözleşme sunuyor. `BackgroundService`, uzun süreli asenkron işler yazmayı kolaylaştırıyor. Hangfire ise kuyruk, kalıcı iş kaydı, zamanlama, retry ve Dashboard gibi ihtiyaçları daha kapsamlı bir altyapıyla karşılıyor.
Benim tercihim, önce işin gereksinimlerini netleştirmek olurdu. Basit bir kontrol döngüsü için `BackgroundService` yeterliyken işlerin korunması, yeniden denenmesi ve operasyonel olarak takip edilmesi gereken senaryolarda Hangfire daha anlamlı hâle geliyor.

Kısacası, en fazla özelliğe sahip aracı seçmek zorunda değiliz. Önemli olan ihtiyacı doğru anlamak ve gereksiz karmaşıklık oluşturmadan çözmek. Yazılım mimarisinde bazen en iyi karar, kullanabileceğimiz bir teknolojiyi kullanmamayı bilmektir.