C# İle Özelleştirilebilir Bir Görev Zamanlayıcı Uygulaması Nasıl Yapılır?

C# İle Özelleştirilebilir Bir Görev Zamanlayıcı Uygulaması Nasıl Yapılır?
C# İle Özelleştirilebilir Bir Görev Zamanlayıcı Uygulaması Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Uygulamamızı geliştirmek için güncel bir geliştirme ortamına ihtiyacınız vardır. .NET 9 SDK'sının yüklü olduğundan emin olmalı ve tercihen Visual Studio 2022 veya VS Code kullanmalısınız. Zamanlayıcı mantığını kurarken herhangi bir dış kütüphane bağımlılığına girmeden, C#'ın yerleşik yeteneklerini kullanacağız.

  • .NET 9 SDK veya üzeri.
  • C# 13 dil özellikleri (isteğe bağlı).
  • Temel asenkron programlama (async/await) bilgisi.
  • Dependency Injection (Bağımlılık Enjeksiyonu) kavramlarına aşinalık.

Temel Görev Arayüzünün Oluşturulması

Özelleştirilebilir bir sistem kurmanın ilk adımı, tüm görevlerin uyması gereken bir sözleşme (interface) tanımlamaktır. Bu sayede, farklı türdeki görevleri tek bir yapı üzerinden yönetebiliriz.

public interface ITaskJob
{
    string Name { get; }
    Task ExecuteAsync(CancellationToken stoppingToken);
}

Yukarıdaki kod bloğu, her görevin bir isme sahip olmasını ve asenkron bir şekilde çalıştırılmasını zorunlu kılar. CancellationToken kullanımı, uygulamanız durdurulduğunda görevlerin güvenli bir şekilde sonlandırılmasını sağlar.

Zamanlayıcı Motorunun Tasarımı

Zamanlayıcı motoru, görevlerin ne zaman çalışacağını belirleyen merkezdir. Burada PeriodicTimer sınıfını kullanarak, döngüsel işlemleri CPU dostu bir şekilde yönetiyoruz.

public class TaskSchedulerEngine
{
    private readonly TimeSpan _interval;
    private readonly ITaskJob _job;

    public TaskSchedulerEngine(ITaskJob job, TimeSpan interval)
    {
        _job = job;
        _interval = interval;
    }

    public async Task StartAsync(CancellationToken ct)
    {
        using PeriodicTimer timer = new(_interval);
        while (await timer.WaitForNextTickAsync(ct))
        {
            await _job.ExecuteAsync(ct);
        }
    }
}

Bu motor, verilen aralıklarla ExecuteAsync metodunu tetikler. PeriodicTimer, geleneksel Thread.Sleep yöntemine göre çok daha performanslıdır ve sistem kaynaklarını tüketmez.

Özelleştirilebilir Görev Örnekleri

Şimdi, tanımladığımız arayüzü kullanarak gerçek bir görev oluşturalım. Örneğin, veritabanı temizleme veya log dosyası arşivleme gibi bir işlem yapabiliriz.

public class DatabaseCleanupJob : ITaskJob
{
    public string Name => "Veritabanı Temizleme Görevi";

    public async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Gerçek bir veritabanı işlemi burada yapılır
        await Task.Delay(1000, stoppingToken); 
        Console.WriteLine($"{Name} başarıyla çalıştırıldı.");
    }
}

Bu sınıfa yeni özellikler ekleyerek (örneğin hata yönetimi veya loglama) görevi daha da özelleştirebilirsiniz. Her yeni görev için sadece ITaskJob arayüzünü implemente etmeniz yeterlidir.

Görev Yönetiminde Karşılaştırma Tablosu

Yöntem Avantaj Dezavantaj
PeriodicTimer Düşük kaynak kullanımı, modern. Basit döngüler için uygundur.
Quartz.NET Gelişmiş özellikler (CRON desteği). Dış bağımlılık gerektirir.
Hangfire Kalıcı görevler, dashboard desteği. Veritabanı bağımlılığı vardır.

Uygulamanın Çalıştırılması ve Test Edilmesi

Uygulamanızı ayağa kaldırmak için bir Host yapısı kurmanız önerilir. Bu, uygulamanın yaşam döngüsünü yönetmenizi kolaylaştırır.

var builder = Host.CreateApplicationBuilder(args);
var host = builder.Build();

var job = new DatabaseCleanupJob();
var engine = new TaskSchedulerEngine(job, TimeSpan.FromSeconds(10));

await engine.StartAsync(CancellationToken.None);

Bu kod bloğu, 10 saniyede bir çalışan basit bir zamanlayıcıyı başlatır. Test etmek için konsol çıktısını izleyebilir ve uygulamanın periyodik olarak mesaj yazdırdığını görebilirsiniz.

Güvenlik Uyarısı: Arka plan görevlerinde veritabanı veya dış servis bağlantılarını yönetirken mutlaka 'try-catch' blokları kullanın. Hataların loglanmaması, uygulamanın sessizce çökmesine ve kritik görevlerin aksamasına neden olabilir.

Sıkça Sorulan Sorular

Zamanlayıcıyı nasıl durdurabilirim?

CancellationTokenSource nesnesini kullanarak Cancel() metodunu çağırdığınızda, WaitForNextTickAsync döngüsü otomatik olarak sonlanacaktır.

Aynı anda birden fazla görev çalıştırabilir miyim?

Evet, her görev için ayrı bir TaskSchedulerEngine örneği oluşturup bunları Task.WhenAll ile paralel başlatabilirsiniz.

Görevler arası veri paylaşımı nasıl yapılır?

Görevler arasında veri paylaşımı için Channel veya ConcurrentQueue gibi thread-safe (iş parçacığı güvenli) koleksiyonları kullanmalısınız.

Hata yönetimi nasıl yapılmalı?

Her görevin kendi try-catch bloğu olmalıdır. Bir görevin başarısız olması, zamanlayıcının geri kalanını etkilememelidir.

Bu yapı üretim ortamında (Production) güvenli mi?

Küçük ve orta ölçekli projeler için uygundur. Ancak çok karmaşık, kalıcılık gerektiren (uygulama kapansa bile devam etmesi gereken) işler için Hangfire gibi kütüphanelere yönelmeniz önerilir.

Kod Güvenliği Sorumluluk Reddi: Bu makalede sunulan kodlar eğitim amaçlıdır. Üretim ortamında kullanmadan önce mutlaka birim testleri (unit tests) yazmalı ve hata yönetim mekanizmalarını projenizin gereksinimlerine göre özelleştirmelisiniz.

Görev Zamanlayıcı İçin İleri Seviye Performans Optimizasyonu

Geliştirdiğimiz zamanlayıcı yapısı, küçük ve orta ölçekli görevler için oldukça verimlidir. Ancak, sistemin yükü arttığında veya görev sayısı yüzlerce seviyesine ulaştığında, kaynak tüketimini optimize etmek kritik bir hale gelir. Özellikle PeriodicTimer kullanırken, görevlerin birbirini bloklamaması için Task.Run ile asenkron bir yapı kurmak hayati önem taşır.

Performansı artırmak için dikkat etmeniz gereken temel noktalar şunlardır:

  • Thread Pool Yönetimi: Çok sayıda kısa süreli görev çalıştırıyorsanız, Task.Run yerine SemaphoreSlim kullanarak eşzamanlı çalışma limitini belirleyin.
  • Bellek Sızıntılarını Önleme: Görevlerinizde IDisposable nesneler kullanıyorsanız, mutlaka using blokları ile kaynakları serbest bırakın.
  • Zamanlama Hassasiyeti: PeriodicTimer, sistem saatine göre değil, bir önceki görevin bitişine göre değil, periyodik olarak tetiklenir. Eğer göreviniz uzun sürüyorsa, bir sonraki tetiklenme süresini geciktirmemek için görevi arka plan thread'ine devredin.
// Performans için SemaphoreSlim kullanımı
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5); // Aynı anda en fazla 5 görev

public async Task ExecuteAsync(CancellationToken ct)
{
    await _semaphore.WaitAsync(ct);
    try
    {
        // Görev mantığı burada çalışır
        await Task.Delay(1000, ct);
    }
    finally
    {
        _semaphore.Release();
    }
}

Zamanlayıcı Uygulamalarında Hata Ayıklama (Debugging) Stratejileri

Zamanlanmış görevler arka planda çalıştığı için, hata oluştuğunda bunları yakalamak normal bir akıştan daha zordur. Hata ayıklama sürecini kolaylaştırmak adına, merkezi bir ILogger yapısı kurmanız ve görevlerin durumunu takip eden bir "State Machine" mantığı geliştirmeniz önerilir.

Görev İzleme İçin İpuçları:

  1. Global Exception Handler: Görevlerin içine try-catch blokları eklemek yerine, görev çalıştırıcı motorunda genel bir hata yakalayıcı kullanın.
  2. Durum Günlüğü (State Logging): Görevin "Başladı", "Bitti" ve "Hata Aldı" durumlarını bir veritabanına veya dosyaya yazın.
  3. CancellationToken Kontrolü: Uygulama kapatılırken görevlerin yarım kalmaması için CancellationToken sinyallerini her adımda kontrol edin.
// Merkezi hata yakalama örneği
public async Task RunSafeAsync(ITaskJob job, CancellationToken ct)
{
    try
    {
        await job.ExecuteAsync(ct);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Görev iptal edildi.");
    }
    catch (Exception ex)
    {
        // Hata loglama servisine gönder
        LogErrorToSystem(job.GetType().Name, ex);
    }
}

Deployment ve Üretim Ortamı Hazırlığı

Uygulamanızı bir Windows Servisi veya Linux üzerinde bir Systemd servisi olarak dağıtmak, uygulamanın sunucu yeniden başlatıldığında otomatik olarak ayağa kalkmasını sağlar. Ayrıca, görevlerin yoğun olduğu durumlarda GC.Collect() gibi manuel bellek yönetimi komutlarından kaçınmalı, bunun yerine .NET'in kendi bellek yönetimi mekanizmalarına güvenmelisiniz.

Sonuç

C# ile özelleştirilebilir bir görev zamanlayıcı oluşturmak, uygulamanızın otomasyon kabiliyetini artırmanın en temiz yoludur. PeriodicTimer ve ITaskJob arayüzü kullanarak kurduğumuz bu modüler yapı, projenizin büyümesine paralel olarak yeni görevleri kolayca entegre etmenize olanak tanır. Bir sonraki adım olarak, bu yapıya bir "Görev Kuyruğu" mekanizması ekleyerek görevlerin önceliklendirilmesini sağlayabilirsiniz.

Bu yazıya tepkinizi paylaşın:
Selin Yılmaz

Kullanıcı odaklı rehberler hazırlama konusunda uzmanım. Adım adım anlatımlarla karmaşık süreçleri herkes için anlaşılır kılıyorum.

Yorumlar (0)

Yorum Yaz