C
C#2mo ago
jiheielf

Worker Service .NET8 how to gracefully shutdown as a docker container

I'm a junior developer just starting to work with Docker, and I have a question regarding graceful shutdown of the worker services. I created a default worker service in Visual Studio, and my Worker class looks something like this:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Do some async work
await myAsyncWork();
await mySecondAsyncWork();
await Task.Delay(1000, stoppingToken);
}
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Do some async work
await myAsyncWork();
await mySecondAsyncWork();
await Task.Delay(1000, stoppingToken);
}
}
After publishing the application as a Docker container, I noticed that when I run docker stop, it immediately aborts my async task inside await myAsyncWork(); or await mySecondAsyncWork();. Is there a way to ensure that when I run docker stop, the application finishes the current async task and finishes the current iteration of while (!stoppingToken.IsCancellationRequested) and then gracefully shuts down? And is there any best practise I should follow?
7 Replies
Sossenbinder
Sossenbinder2mo ago
Sounds like you're looking for IApplicationLifetime Let me try to find a resource
Sossenbinder
Sossenbinder2mo ago
Andrew Lock | .NET Escapades
Introducing IHostLifetime and untangling the Generic Host startup i...
In this post I introduce the new IHostLifetime interface and look at the interactions involved in the ASP.NET Core generic host startup and shutdown processes
Sossenbinder
Sossenbinder2mo ago
Ah, right, it was renamed to IHostLifetime I think the best ideal solution would be to design the app in a way which is not dependant on clean shutdown, which can't be guaranteed if there's some higher forces like power outage
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
jcotton42
jcotton424w ago
SIGTEM, a timeout, then SIGKILL iirc
jiheielf
jiheielfOP4w ago
Hi all, so I have tried to search for any lifecycle related stuff for the worker service, and I have found the interface called IHostedLifecycleService. So in my application I will have a boolean value that will be changed by the long run process and in the StoppingAsync method it will just wait until the process change the boolean. But it seems that just won't work for docker container...any additional ideas?
namespace MyWorkerService
{
public class Worker : BackgroundService, IHostedLifecycleService
{
private readonly ILogger<Worker> _logger;
private volatile bool myBool = true;

public Worker(ILogger<Worker> logger)
{
_logger = logger;
}

public Task StartingAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

public Task StartedAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

public Task StoppingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Stopping - performing cleanup");

// Start cleanup in background
Task.Run(() =>
{
Thread.Sleep(3000); // Simulate cleanup work
_logger.LogInformation("Cleanup finished");
myBool = false; // This will allow the loop to exit
});

// Wait for the cleanup to complete
while (myBool)
{
Thread.Sleep(1000); // Short sleep to avoid CPU spinning
}

_logger.LogInformation("Stopping completed");
return Task.CompletedTask;
}

public Task StoppedAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
}
namespace MyWorkerService
{
public class Worker : BackgroundService, IHostedLifecycleService
{
private readonly ILogger<Worker> _logger;
private volatile bool myBool = true;

public Worker(ILogger<Worker> logger)
{
_logger = logger;
}

public Task StartingAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

public Task StartedAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

public Task StoppingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Stopping - performing cleanup");

// Start cleanup in background
Task.Run(() =>
{
Thread.Sleep(3000); // Simulate cleanup work
_logger.LogInformation("Cleanup finished");
myBool = false; // This will allow the loop to exit
});

// Wait for the cleanup to complete
while (myBool)
{
Thread.Sleep(1000); // Short sleep to avoid CPU spinning
}

_logger.LogInformation("Stopping completed");
return Task.CompletedTask;
}

public Task StoppedAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
}
Unknown User
Unknown User4w ago
Message Not Public
Sign In & Join Server To View

Did you find this page helpful?