Fix: BackgroundService CancellationToken Error - Dev Fix in 30 Seconds

Published: Jun 04, 2026 · By Kumar Kunal

The Error

TaskCanceledException: A task was canceled
// Or: BackgroundService not stopping gracefully on app shutdown

Quick Fix - 2 Minutes

// Dev Fix: Proper BackgroundService pattern.NET 8
public class Worker : BackgroundService
{
    private readonly ILogger _logger;
public Worker(ILogger<Worker> logger) => _logger = logger;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested) // Dev Fix: Check token
    {
        try
        {
            await DoWorkAsync(stoppingToken); // Pass token down
            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); // Dev Fix: Delay respects cancellation
        }
        catch (OperationCanceledException)
        {
            // Dev Fix: Expected on shutdown, don't log as error
            break;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in worker");
            await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); // Backoff
        }
    }
}

public override async Task StopAsync(CancellationToken cancellationToken)
{
    _logger.LogInformation("Worker stopping"); // Dev Fix: Cleanup here
    await base.StopAsync(cancellationToken);
}

}

Why This Happens

BackgroundService runs until app stops. If you don't check stoppingToken, Task.Delay throws TaskCanceledException..NET 8 Host will force-kill after 5 sec if you don't handle shutdown.

Real-World Scenario: Queue Processor + HttpClient

Most common production bug. Your worker calls an API but doesn't cancel the HttpClient:

public class QueueWorker : BackgroundService
{
    private readonly IHttpClientFactory _httpFactory;
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var message = await GetNextMessage(stoppingToken);
            
            // WRONG: No token passed to HttpClient
            var client = _httpFactory.CreateClient();
            await client.PostAsync("https://api.example.com/webhook", content);
            
            // If app shuts down here, PostAsync hangs for 100s then throws TaskCanceledException
        }
    }
}

// FIX: Pass stoppingToken to every async call var response = await client.PostAsync("https://api.example.com/webhook", content, stoppingToken); // Now cancels in 5 seconds on shutdown instead of hanging

Why this matters: Without the token, K8s/Docker sends SIGTERM, your app waits 30s for HttpClient timeout, then SIGKILL drops the pod. You lose the message. With token, you exit cleanly in 5s.

Related Fixes You Should Know

BackgroundService issues cascade into:

FAQ

Q: Why do I get TaskCanceledException when stopping my app?

That's normal. When you shut down, .NET cancels the stoppingToken. Any Task.Delay or HttpClient call using that token throws OperationCanceledException. Catch it and exit gracefully.

Q: How long does BackgroundService wait to stop in.NET 8?

Default is 5 seconds. If your StopAsync doesn't finish, the host force-kills it. Set HostOptions.ShutdownTimeout to increase, but fix your code instead.

Best Practice for.NET 8

  1. Always pass stoppingToken to delays + HTTP calls
  2. Catch OperationCanceledException - it's normal on shutdown
  3. Override StopAsync for cleanup: flush logs, close connections
  4. For long work: split into chunks and check token between chunks

Related Dev Fixes

Found this helpful?

Master C# with our complete course. Real apps, real skills, job-ready in 2 hours.

Share this fix: Twitter LinkedIn

Comments on Fix: BackgroundService CancellationToken Error - Dev Fix in 30 Seconds (0)

No comments yet. Be the first to share your thoughts!