Killing a Worker Application from a BackgroundService

Download full source code.

If you have an application that runs a BackgroundService or maybe a few BackgroundServices, you may want to stop the host application and all the other services from one of the BackgroundServices .

The first approach I tried was to pass a CancellationTokenSource to the service using dependency injection and then call Cancel() on it. The host application monitors the CancellationToken, and when it is cancelled, it stops the host application.

But then I found another way that is probably a little more “correct”. I pass the IHostApplicationLifetime to the service and call StopApplication() on it. This will stop the host application and all the other services that might be running.

This blog post will show you how to do this. In another post, I show the other approach mentioned above.

1. Create the project

Create a worker project -

dotnet new worker -n KillHostFromWorker

2. Update the Program.cs

Open the project and update the Program.cs file to look like this -

 1using KillHostFromWorker;
 2
 3var builder = Host.CreateApplicationBuilder(args);
 4builder.Services.AddHostedService<Worker>(); // simple worker
 5builder.Services.AddHostedService<WorkerCancelHost>(); // worker that cancels the host
 6
 7var host = builder.Build();
 8host.Run();
 9
10Console.WriteLine("Host exiting");

This adds two workers to the host. The first one is a simple worker that prints out a count. The second one is the worker that will cancel the host after running for a few seconds.

2. Update the Worker.cs

This is not strictly necessary, but it will make the output a little clearer, especially when this worker gets cancelled during its Task.Delay(...) call. Open the Worker.cs file and update it to look like this -

 1namespace KillHostFromWorker;
 2
 3public class Worker : BackgroundService
 4{
 5    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 6    {
 7        int count = 0;
 8        while (!stoppingToken.IsCancellationRequested)
 9        {
10            Console.WriteLine($"Worker: count = {++count} ");
11            try 
12            {
13                await Task.Delay(1000, stoppingToken);
14            }
15            catch (TaskCanceledException)
16            {
17                Console.WriteLine("Worker: Task was cancelled during Task.Delay");
18            }
19        }
20        Console.WriteLine("Worker: Exiting");
21    }
22}

3. Add a WorkerCancelHost.cs file

This is the worker that will cancel the host, and in turn, all other workers.

Create a file class named WorkerCancelHost and add the following code -

 1namespace KillHostFromWorker;
 2
 3public class WorkerCancelHost(IHostApplicationLifetime hostApplicationLifetime) : BackgroundService
 4{
 5    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 6    {
 7        int count = 0;
 8        while (!stoppingToken.IsCancellationRequested)
 9        {
10            Console.WriteLine($"\tWorkerCancelHost: count = {++count}");
11
12            if(count >= 3)
13            {
14                Console.WriteLine("\tWorkerCancelHost: Cancelling host from WorkerCancelHost");
15                hostApplicationLifetime.StopApplication();
16                break;
17            }
18
19            try 
20            {
21                await Task.Delay(1000, stoppingToken);
22            }
23            catch (TaskCanceledException)
24            {
25                Console.WriteLine("\tWorkerCancelHost: Task was cancelled during Task.Delay");
26                break;
27            }
28        }
29        Console.WriteLine("\tWorkerCancelHost: Exiting");
30    }
31}

Note, on line 3, the IHostApplicationLifetime is passed to the constructor.

It is then used to call StopApplication() after the worker has run for three seconds.

4. Run the application

Run the application and you should see output like this -

Worker: count = 1 
        WorkerCancelHost: count = 1
        WorkerCancelHost: count = 2
Worker: count = 2 
Worker: count = 3 
        WorkerCancelHost: count = 3
        WorkerCancelHost: Cancelling host from WorkerCancelHost
        WorkerCancelHost: Exiting
Worker: Task was cancelled during Task.Delay
Worker: Exiting
Host exiting

When working with applications where multiple events can terminate it, it is always a good idea to test the cancellation paths.

Now try running it, but press Ctrl+C to cancel the host. You should see output like this -

Worker: count = 1 
        WorkerCancelHost: count = 1
        WorkerCancelHost: count = 2
Worker: count = 2 
^C      WorkerCancelHost: Task was cancelled during Task.Delay
        WorkerCancelHost: Exiting
Worker: Task was cancelled during Task.Delay
Worker: Exiting
Host exiting

Download full source code.

comments powered by Disqus

Related