Isaac.

Background Jobs with Hangfire

Schedule and execute background jobs reliably with Hangfire.

By EMEPublished: February 20, 2025
hangfirebackground jobsschedulingreliable execution

A Simple Analogy

Hangfire is like a personal task scheduler. It remembers your tasks, executes them even if the app restarts, and retries if something goes wrong.


Why Background Jobs?

  • Non-blocking: Don't wait for long operations
  • Reliable: Retry on failure
  • Scheduled: Run at specific times
  • Monitoring: Track job status
  • Scalable: Distribute across workers

Setup

// Install NuGet package
// Install-Package Hangfire.AspNetCore

builder.Services.AddHangfire(configuration => configuration
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));

builder.Services.AddHangfireServer();

var app = builder.Build();

app.UseHangfireDashboard("/hangfire");
app.UseHangfireServer();

Fire and Forget Jobs

public class EmailService
{
    private readonly ILogger<EmailService> _logger;
    
    public void SendEmail(string email, string subject, string body)
    {
        _logger.LogInformation("Sending email to {Email}", email);
        Thread.Sleep(2000);  // Simulate slow operation
        _logger.LogInformation("Email sent successfully");
    }
}

// Enqueue job
var client = new BackgroundJobClient();

client.Enqueue<EmailService>(x => x.SendEmail(
    "user@example.com",
    "Welcome!",
    "Welcome to our service"));

// Response returned immediately

Scheduled Jobs

public class ReportService
{
    public void GenerateDailyReport(string date)
    {
        Console.WriteLine($"Generating report for {date}");
        // Expensive operation
    }
}

// Schedule for later
var client = new BackgroundJobClient();

client.Schedule<ReportService>(
    x => x.GenerateDailyReport(DateTime.Now.ToString("yyyy-MM-dd")),
    TimeSpan.FromMinutes(10));

// Or specific time
client.Schedule<ReportService>(
    x => x.GenerateDailyReport("2025-02-20"),
    new DateTimeOffset(2025, 2, 20, 6, 0, 0, TimeSpan.Zero));

Recurring Jobs

// In Startup or Program.cs
var recurringJobManager = new RecurringJobManager();

// Daily job
recurringJobManager.AddOrUpdate(
    "daily-cleanup",
    () => new CleanupService().DeleteOldFiles(),
    Cron.Daily(6)  // Every day at 6 AM
);

// Every 5 minutes
recurringJobManager.AddOrUpdate(
    "health-check",
    () => new HealthCheckService().Check(),
    Cron.MinuteInterval(5)
);

// Custom cron
recurringJobManager.AddOrUpdate(
    "weekly-report",
    () => new ReportService().Generate(),
    "0 6 ? * MON"  // 6 AM every Monday
);

// Remove job
recurringJobManager.RemoveIfExists("daily-cleanup");

Retry Policies

client.Enqueue<DataService>(x => x.ImportData(file), 
    new EnqueuedState { Reason = "Manual enqueue" });

// Automatic retries configured in Hangfire options
builder.Services.AddHangfire(configuration => configuration
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseSqlServerStorage(connectionString)
    .WithJobExpirationTimeout(TimeSpan.FromDays(7))
);

// In job method - manual retry
public void ProcessWithRetry(string data)
{
    try
    {
        ProcessData(data);
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException("Retry", ex);
    }
}

Job Progress and Cancellation

public class LongRunningService
{
    public void ProcessLargeDataset(string id, IJobCancellationToken token)
    {
        var items = GetItems(id);
        
        foreach (var item in items)
        {
            if (token.ShutdownToken.IsCancellationRequested)
                break;
            
            ProcessItem(item);
            token.ThrowIfCancellationRequested();
        }
    }
}

// Client enqueue
var jobId = BackgroundJob.Enqueue<LongRunningService>(
    x => x.ProcessLargeDataset("data-123", JobCancellationToken.Null));

// Monitor progress via dashboard

Best Practices

  1. Make jobs idempotent: Safe to run multiple times
  2. Log heavily: Track job execution
  3. Set timeouts: Prevent hanging jobs
  4. Monitor dashboard: Check job health
  5. Use persistent storage: SQL Server or Redis

Related Concepts

  • Job queues
  • Distributed processing
  • Cron scheduling
  • Async task processing

Summary

Hangfire provides reliable, persistent background job execution with retry logic, scheduling, and monitoring. Use it for async tasks that shouldn't block user requests.