A typical web application follows the request–response model. A client sends a request, the server processes the request, and then returns a response.
So naturally we might think that if there is no request, no code will execute.
However many real-world applications need to run tasks continuously in the background.
ASP.NET Core provides the IHostedService interface to support such scenarios.
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
StartAsync runs when the application starts and StopAsync runs when the application stops.
Add the hosted service inside Program.cs:
builder.Services.AddHostedService<ApplicationLifetimeService>();
public class ApplicationLifetimeService : IHostedService
{
private readonly IHostApplicationLifetime _appLifetime;
public ApplicationLifetimeService(IHostApplicationLifetime appLifetime)
{
_appLifetime = appLifetime;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_appLifetime.ApplicationStopping.Register(OnStopping);
_appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void OnStopping()
{
Console.WriteLine("ApplicationStopping event fired. Saving data...");
}
private void OnStopped()
{
Console.WriteLine("ApplicationStopped event fired.");
}
}
public class ApplicationLifetimeService : IHostedService
{
private readonly IHostApplicationLifetime _appLifetime;
private readonly ILogger<ApplicationLifetimeService> _logger;
public ApplicationLifetimeService(
IHostApplicationLifetime appLifetime,
ILogger<ApplicationLifetimeService> logger)
{
_appLifetime = appLifetime;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("ApplicationLifetimeService starting.");
_appLifetime.ApplicationStarted.Register(OnStarted);
_appLifetime.ApplicationStopping.Register(OnStopping);
_appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("StopAsync triggered.");
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("Application started.");
}
private void OnStopping()
{
_logger.LogInformation("Application stopping.");
}
private void OnStopped()
{
_logger.LogInformation("Application stopped.");
}
}
A common approach is using System.Threading.Timer.
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
However timers run on the .NET ThreadPool. If the previous execution has not finished, another thread may start executing the callback.
This can cause thread-safety problems.
private readonly ConcurrentDictionary<string,int> _requestStats = new();
private void DoWork(object? state)
{
string[] keys =
{
"India","Nepal","Bhutan","Sri-Lanka","Japan",
"USA","UK","Canada","Australia","New-Zealand","Netherlands"
};
string key = keys[_random.Next(keys.Length)];
_requestStats.AddOrUpdate(
key,
1,
(k, oldValue) => oldValue + 1);
_logger.LogInformation($"Stats updated for {key}");
}
To allow ASP.NET Core to run cleanup logic, the application should be stopped using Ctrl + C.
Stopping directly from Visual Studio kills the process immediately and lifecycle events may not execute.
ASP.NET Core provides a cleaner approach using BackgroundService.
public class ApplicationLifetimeService : BackgroundService
{
private readonly ILogger<ApplicationLifetimeService> _logger;
private readonly Dictionary<string, int> _requestStats = new();
private readonly Random _random = new Random();
public ApplicationLifetimeService(ILogger<ApplicationLifetimeService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Background service started.");
while (!stoppingToken.IsCancellationRequested)
{
DoWork();
try
{
await Task.Delay(5000, stoppingToken);
}
catch (TaskCanceledException)
{
}
}
_logger.LogInformation("Background service stopping.");
SaveDataToDisk();
}
private void DoWork()
{
string[] keys =
{
"India","Nepal","Bhutan","Sri-Lanka","Japan",
"USA","UK","Canada","Australia","New-Zealand","Netherlands"
};
string key = keys[_random.Next(keys.Length)];
if (_requestStats.ContainsKey(key))
_requestStats[key]++;
else
_requestStats[key] = 1;
_logger.LogInformation($"Stats updated for {key}");
}
private void SaveDataToDisk()
{
string json = JsonSerializer.Serialize(_requestStats, new JsonSerializerOptions
{
WriteIndented = true
});
File.WriteAllText("requestStats.json", json);
}
}
{
"Bhutan": 3,
"Australia": 2,
"UK": 1,
"India": 3,
"Nepal": 1,
"New-Zealand": 1,
"Sri-Lanka": 1
}
You can watch the full step-by-step explanation here:
I provide personal one-to-one training for developers who want to master:
Website:
https://supernovaservices.com
WhatsApp for training inquiries:
+91-9331897923
To keep every session productive and distraction-free, please follow these simple guidelines:
Following these guidelines helps you focus better and ensures I can deliver the best learning experience in every class.
I prefer to start with a short 10-minute free call so I can understand:
Why? Because course content, teaching pace, and fees all depend on your needs — there’s no “one-size-fits-all” pricing. Please leave your details below, and I’ll get back to you to arrange a convenient time for the call.
Note: Payment is made only after your first class, once you’re completely satisfied. However, fees paid after the first class are non-refundable. This helps maintain scheduling commitments and allows me to reserve your preferred time slot with full attention.