HHangfire Handbook

TEMEL

Delayed Jobs

Delayed job'lar belirli bir süre sonra veya belirli bir tarihte çalıştırılan tek seferlik görevlerdir.

Schedule() TimeSpan / DateTime Scheduled Bekleme süresi Enqueued Süre doldu Processing

Karar Rehberi

Durum Öneri Örnek veya gerekçe
Sipariş timeout (30 dk) Uygun Ödenmemiş siparişi iptal et
Hoşgeldin e-postası (1 saat) Uygun Kayıt sonrası onboarding
Token expiry temizliği Uygun Refresh token silme
Tam dakika hassasiyeti gereken Uygun değil: polling interval etkiler Borsa emir execution

Kullanım

// TimeSpan ile — "şu kadar sonra"
BackgroundJob.Schedule<IOrderService>(
    svc => svc.CancelUnpaidOrderAsync(orderId),
    TimeSpan.FromMinutes(30));

// DateTimeOffset ile — "bu tarihte"
BackgroundJob.Schedule<IReminderService>(
    svc => svc.SendReminderAsync(userId, "Toplantınız 1 saat sonra"),
    new DateTimeOffset(2026, 6, 15, 9, 0, 0, TimeSpan.FromHours(3)));

// Job ID döner (string) — iptal için kullanılabilir
string jobId = BackgroundJob.Schedule<IOrderService>(
    svc => svc.CancelUnpaidOrderAsync(orderId),
    TimeSpan.FromMinutes(30));

// Sipariş ödendiyse job'ı iptal et
BackgroundJob.Delete(jobId);

Sipariş Timeout Pattern

public class OrderService : IOrderService
{
    private readonly IOrderRepository _repo;

    public async Task CancelUnpaidOrderAsync(int orderId)
    {
        var order = await _repo.GetByIdAsync(orderId);

        // İdempotent kontrol — zaten ödendiyse veya iptal edildiyse bir şey yapma
        if (order is null || order.Status != OrderStatus.PendingPayment)
            return;

        order.Status = OrderStatus.Cancelled;
        order.CancelReason = "Ödeme süresi doldu (30 dakika)";
        order.CancelledAt = DateTimeOffset.UtcNow;

        await _repo.UpdateAsync(order);

        // Stok iade
        BackgroundJob.Enqueue<IStockService>(
            svc => svc.ReleaseReservedStockAsync(orderId));
    }
}

// Sipariş oluşturulduğunda
public int CreateOrder(CreateOrderRequest request)
{
    var order = _mapper.Map<Order>(request);
    order.Status = OrderStatus.PendingPayment;
    _repo.Add(order);

    // 30 dk sonra kontrol et
    var jobId = BackgroundJob.Schedule<IOrderService>(
        svc => svc.CancelUnpaidOrderAsync(order.Id),
        TimeSpan.FromMinutes(30));

    // Job ID'yi sakla — ödeme gelirse iptal edeceğiz
    order.CancelJobId = jobId;
    _repo.Update(order);

    return order.Id;
}

// Ödeme geldiğinde
public void MarkAsPaid(int orderId)
{
    var order = _repo.GetById(orderId);
    order.Status = OrderStatus.Paid;

    // Timeout job'ını iptal et
    if (!string.IsNullOrEmpty(order.CancelJobId))
        BackgroundJob.Delete(order.CancelJobId);

    _repo.Update(order);
}

Scheduling hassasiyeti: Hangfire, scheduled job'ları polling ile kontrol eder. Varsayılan SchedulePollingInterval 15 saniyedir. Yani "30 dakika sonra" demek "30:00 ile 30:15 arası" anlamına gelir. Saniye hassasiyeti gerekiyorsa Hangfire doğru araç değildir.

Örnek: Bir fintech uygulamasında kredi başvurusu yapıldığında, 48 saat içinde belge yüklenmezse başvuru otomatik reddedilir. Delayed job ile bu süre yönetilir, belge yüklendiğinde job silinir.