Published on
🍵 2 min read

Background Jobs in .NET

Authors

Photo

Overview

What are Background Jobs?

Background jobs are tasks that run "behind the scenes" in your application. They handle work that doesn't need to happen immediately or that might take a long time to complete.

Why Use Background Jobs?

  1. Better Performance: Your app stays fast because heavy tasks run separately.
  2. Improved User Experience: Users don't have to wait for long tasks to finish.
  3. Reliability: If a background job fails, it doesn't crash your whole application.

How to Implement Background Jobs in .NET

.NET offers several ways to create background jobs:

  1. IHostedService
  2. BackgroundService
  3. Hangfire

A Simple Example

Here's a basic example using BackgroundService:

public class MyBackgroundJob : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Do some work here
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
}

This job will run every 5 minutes until the application shuts down.

Cron job example using IHostedService

public abstract class CronJobService : IHostedService, IDisposable
{
    private System.Timers.Timer _timer;
    private readonly CronExpression _expression;
    private readonly TimeZoneInfo _timeZoneInfo;

    protected CronJobService(string cronExpression, TimeZoneInfo timeZoneInfo)
    {
        _expression = CronExpression.Parse(cronExpression);
        _timeZoneInfo = timeZoneInfo;
    }

    public virtual async Task StartAsync(CancellationToken cancellationToken)
    {
        await ScheduleJob(cancellationToken);
    }

    protected virtual async Task ScheduleJob(CancellationToken cancellationToken)
    {
        var next = _expression.GetNextOccurrence(DateTimeOffset.Now, _timeZoneInfo);
        if (next.HasValue)
        {
            var delay = next.Value - DateTimeOffset.Now;
            if (delay.TotalMilliseconds <= 0)   // prevent non-positive values from being passed into Timer
            {
                await ScheduleJob(cancellationToken);
            }
            _timer = new System.Timers.Timer(delay.TotalMilliseconds);
            _timer.Elapsed += async (sender, args) =>
            {
                _timer.Dispose();  // reset and dispose timer
                _timer = null;

                if (!cancellationToken.IsCancellationRequested)
                {
                    await DoWork(cancellationToken);
                }

                if (!cancellationToken.IsCancellationRequested)
                {
                    await ScheduleJob(cancellationToken);    // reschedule next
                }
            };
            _timer.Start();
        }
        await Task.CompletedTask;
    }

    public virtual async Task DoWork(CancellationToken cancellationToken)
    {
        await Task.Delay(5000, cancellationToken);  // do the work
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        _timer?.Stop();
        await Task.CompletedTask;
    }

    public virtual void Dispose()
    {
        _timer?.Dispose();
    }
}