Reputation: 1069
I'm developing a windows service using .Net Core 3.1
Lots of online resource suggesting me to use BackgroundService
for it. Problem is I cannot get the service to be stopped gracefully, by just overriding ExecuteAsync
method.
The codes setup for testing is very simple:
public class MyService: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Console.WriteLine($"Service started at {DateTimeOffset.Now}");
await DoLongRunningWork(stoppingToken);
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Service is being stopped...");
await base.StopAsync(cancellationToken);
Console.WriteLine("Service stopped at {DateTimeOffset.Now}");
}
private static async Task DoLongRunningWork(CancellationToken token)
{
Console.WriteLine("I'm working...");
while (!token.IsCancellationRequested)
{
Console.WriteLine($"\t... doing work ...");
await Task.Delay(1000, token);
}
// THE BELOW LINE IS NEVER REACHED. i.e The line "Cancel requested? True" is never logged out...
Console.WriteLine($"Cancel requested? {token.IsCancellationRequested}");
}
}
And the Main
method
static void Main(string[] args)
{
Console.WriteLine("Starting My Service...");
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices(services =>
{
services.AddHostedService<MyService>();
})
.Build()
.Run();
}
One thing I can do is implementing my own Stop
method and calling it in my override StopAsync
. But I'm curious why this simple setup didn't work as expected.
I have read the BackgroundService.cs source code, it looks like it should work the way I expected (i.e. this line Cancel requested? True
should be logged out but it didn't...
I have tried both running it as a console app and stopping it using Ctrl+C
, as well as installed it as Windows Service and using service.msc
to control it. Same behaviour (i.e. no gracefully shutdown...)
Any helps are much appreciated!
Upvotes: 3
Views: 4342
Reputation: 7067
I was having the same issue while following the official examples, and catching the TaskCanceledException
did the trick, here's how my code looks now:
public class Worker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Started");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(60000, stoppingToken);
}
catch (TaskCanceledException)
{
continue;
}
}
_logger.LogInformation("Stopped");
}
}
I'm now getting the "Stopped" log :)
Upvotes: 1
Reputation: 1069
I forgot about this for such a long time. And when I come back to it, I found out the issue... It was just a confusion
This particular line
await Task.Delay(1000, token);
will throw TaskCancelledException
hence the loop will be exited the method will stop... @Jeremy Lakeman did comment about this but I didn't think it through at that time I guessed...
I'll just need to be careful with using await Task.Delay(..., token)
with a cancellation token...
So, I created a simple extension
public static async Task SafeDelay(this Task source)
{
try
{
await source;
}
catch (TaskCanceledException)
{
// I don't want a throw on TaskCanceledException...
}
}
private static async Task DoLongRunningWork(CancellationToken token)
{
Console.WriteLine("I'm working...");
while (!token.IsCancellationRequested)
{
Console.WriteLine($"\t... doing work ...");
// I simply want the loop to keep running, I don't want an absurd stop...
await Task.Delay(1000, token).SafeDelay();
}
// This line is now reached...
Console.WriteLine($"Cancel requested? {token.IsCancellationRequested}");
}
Upvotes: 3