Isaac.

dotnet

Background Jobs with Hangfire

Schedule and execute background jobs reliably with Hangfire.

By Emem IsaacNovember 9, 20213 min read
#hangfire#background jobs#scheduling#reliable execution
Share:

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.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours