Doing Some Cleanup in a BackgroundService
Download full source code.
There is something a little strange about the Worker Service template and its BackgroundService
in .NET.
You can create a new Worker Service project using -
`dotnet new worker -n MyWorker
The Problem
The Worker
class uses a BackgroundService
as its base class.
The Worker
has an ExecuteAsync(...)
method that is called when the service is started, and it takes a CancellationToken
as a parameter.
So far, so good.
There is a while
loop in the ExecuteAsync(...)
method that checks if the CancellationToken
is canceled. If it is canceled, the loop exits, and anything outside the loop should be executed.
Still ok.
There is a Task.Delay(1000, stoppingToken)
call in the loop that delays for 1 second and takes a CancellationToken
as a parameter.
My Worker
does very little, so the delay during each iteration of the loop is long compared to the time the actual work takes. Any cancellation is likely to happen during the delay period.
If the CancellationToken
is canceled during the delay, the Task.Delay(...)
should throw a TaskCanceledException
.
But it does not.
The ExecuteAsync(...)
method stops running entirely, but no exception is thrown.
Any code outside the while
loop is not executed.
This means that any cleanup code I want to run when the BackgroundService
is canceled will likely not run because cancellation usually happens during the delay.
Why does this happen?
I don’t know.
If I run a simple console application with a loop around Task.Delay(1000, stoppingToken)
call and cancel the CancellationToken
, the Task.Delay(...)
throws a TaskCanceledException
and I see the exception.
1CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000);
2CancellationToken stoppingToken = cancellationTokenSource.Token;
3
4int count = 0;
5while(!stoppingToken.IsCancellationRequested)
6{
7 Console.WriteLine($"{++count} - {DateTime.Now:HH:mm:ss}");
8 await Task.Delay(1000, stoppingToken);
9}
10
11Console.WriteLine("Exiting");
The output of this will be
1 - 15:33:27
2 - 15:33:28
3 - 15:33:29
Unhandled exception. System.Threading.Tasks.TaskCanceledException: A task was canceled.
at Program.<Main>$(String[] args) in /home/bryan/dev/blog/2025/TaskDelayWithCancellation/Program.cs:line 8
at Program.<Main>(String[] args)
The TaskCanceledException
is thrown as you would expect.
But the BackgroundService
does not throw the exception!
The Solution
The solution is to put a try...catch
block around the call to Task.Delay(1000, stoppingToken)
, catching the TaskCanceledException
.
In my use case, I want to do some cleanup when the BackgroundService
is canceled. To make sure this happens I need to immediately exit the while
loop and run a few more lines of code.
Here is the updated Worker
class -
1protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2{
3 while (!stoppingToken.IsCancellationRequested)
4 {
5 Console.WriteLine($"Worker running at: {DateTime.Now:HH:mm:ss}");
6 //await Task.Delay(1000, stoppingToken); // does not throw TaskCanceledException and won't execute the cleanup code
7
8 try
9 {
10 await Task.Delay(1000, stoppingToken);
11 }
12 catch (TaskCanceledException)
13 {
14 Console.WriteLine($"Cancelled during the Task.Delay(...).");
15 break;
16 }
17 Console.WriteLine($"Still in loop running at: {DateTime.Now:HH:mm:ss}");
18
19 }
20 Console.WriteLine("Cancellation requested, doing some cleanup.");
21}
From what I can see the explicit try...catch
block is the best way to immediately cancel the Task.Delay(1000, stoppingToken)
and exit the while
loop.
If you go with other options like Task.Delay(1000)
, you need to wait until the delay is over before the while
loop exits. But also, any code after the delay will still run.
If you try things like -
1await Task.Delay(1000, stoppingToken).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing)
2// or
3await Task.Delay(1000, stoppingToken).ContinueWith(t => { return t.Exception == default;});
They won’t exit the while
loop either, again running code that comes after the delay.
Download full source code.