NetCore HostService
In ASP.NET Core, background tasks can be implemented as hosted services, A hosted service is a class with background task logic that implements the IHostedService interface. This article provies three hosted service examples:
- Background task that runs on a timer.
- Hosted service that activates as scoped service. The scoped service can use Dependency injection(DI),
- Queued background tasks that run sequentially.
IHostedService interface
The IHostedService interface defines two methods for objects that are managed by the host:
- StartAsync(CancellatlionToken)
StartAsync contains the logic to start the background task. StartAsync is called before:
The app's request processing pipeline is configured.
The Server is started and IApplicationLifetime.ApplicationStarted is triggered.
The StartAsync should be limited to short running tasks because hosted services are run sequentially, and no further services are started until StartAsync runs to completion.
- StopAsync(CancellationToken)
StopAsync is triggered when teh host is performing a graceful shutdown. StopAsync contains the logic to end the background task, implemnet IDisposable and finalizers to dispose of any unmannaged resources.
The cancellation token has a default 30 second timeout to indicate that the shutdown process should no longer be graceful. When cancellation is requested on the token:
- Any remaining background operations that the app is performing should be aborted.
- Any methods called in
StopAsync
should return promptly.
However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.
If the app shuts down unexpectedly (for example, the app's process fails), StopAsync
might not be called. Therefore, any methods called or operations conducted in StopAsync
might not occur.
To extend the default 30 second shutdown timeout, set:
- ShutdownTimeout when using Generic Host. For more information, see .NET Generic Host in ASP.NET Core.
- Shutdown timeout host configuration setting when using Web Host. For more information, see ASP.NET Core Web Host.
The hosted service is activated once at app startup and gracefully shut down at app shutdown. If an error is thrown during background task execution, Dispose
should be called even if StopAsync
isn't called.
Timed backgound tasks

public class TimedHostedService: IHostedService, IDisposable { private int excutionCount = 0; private readonly ILogger<TimedHostedService> _logger; private Timer? _timer = null; public TimedHostedService(ILogger<TimedHostedService> logger) { this._logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Hosted Service running.."); for (int i = 0; i < 5; i++) { Task.Run(() => { _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); }); } return Task.CompletedTask; } private void DoWork(object? state) { var count = Interlocked.Increment(ref excutionCount); _logger.LogInformation($"Timed Hosted Service is working. count:{count}."); } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Hosted Service is stopping."); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _logger.LogInformation($"Dispose timer object."); _timer?.Dispose(); } }
The Timer doesn't wait for previous executions of DoWork
to finish, so the approach shown might not be suitable for every scenario. Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update executionCount
concurrently.
Consuming a scoped service in a background task

public class ConsumeScopedServiceHostedService : BackgroundService { private readonly ILogger<ConsumeScopedServiceHostedService> _logger; public IServiceProvider _serviceProvider { get; } public ConsumeScopedServiceHostedService(IServiceProvider serviceProvider, ILogger<ConsumeScopedServiceHostedService> logger) { _serviceProvider = serviceProvider; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Consume Scoped Service Hosted Service running."); await DoWork(stoppingToken); } private async Task DoWork(CancellationToken stoppingToken) { _logger.LogInformation("Consume Scoped Service Hosted Service is working."); using (var scope = _serviceProvider.CreateScope()) { var scopedProcessingService = scope.ServiceProvider.GetService<IScopeProcessingService>(); await scopedProcessingService.DoWork(stoppingToken); } } public override async Task StopAsync(CancellationToken stoppingToken) { _logger.LogInformation("Consume Scope Service Hosted Service is stopping."); await base.StopAsync(stoppingToken); } }

internal interface IScopeProcessingService { Task DoWork(CancellationToken stoppingToken); } internal class ScopedProcessingService : IScopeProcessingService { private int excutionCount = 0; private readonly ILogger _logger; public ScopedProcessingService(ILogger<ScopedProcessingService> logger) { _logger = logger; } public async Task DoWork(CancellationToken stoppingToken) { while(!stoppingToken.IsCancellationRequested) { excutionCount++; _logger.LogInformation($"Scopped Processing Service is working. count: {excutionCount}"); await Task.Delay(10000,stoppingToken); } } }
Queued background tasks

public interface IBackgroundTaskQueue { ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem); ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(CancellationToken cancellationToken); } public class BackgroundTaskQueue : IBackgroundTaskQueue { private readonly Channel<Func<CancellationToken, ValueTask>> _queue; public BackgroundTaskQueue(int capacity) { //Capacity should be set based on the expected application load and number of concurrent threads accessing the queue. //BoundedChannelFullModel.Wait will cause calls to WritAsync() to return a task, //which completes only when space became available. this leads to backpressure, //in case too many publisher/calls start accumulating. var options = new BoundedChannelOptions(capacity) { FullMode = BoundedChannelFullMode.Wait }; _queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options); } public async ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken,ValueTask> workItem) { if(workItem ==null) throw new ArgumentNullException(nameof(workItem)); await _queue.Writer.WriteAsync(workItem); } public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(CancellationToken cancellationToken) { var workItem = await _queue.Reader.ReadAsync(cancellationToken); return workItem; } }

public class QueuedHostedService : BackgroundService { private readonly ILogger<QueuedHostedService> _logger; public IBackgroundTaskQueue _taskQueue; public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILogger<QueuedHostedService> logger) { this._taskQueue = taskQueue; this._logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation($"Queued Hosted Service is running. background queue."); while (!stoppingToken.IsCancellationRequested) { var workItem = await _taskQueue.DequeueAsync(stoppingToken); try { await workItem(stoppingToken); _logger.LogDebug($"The work item is excuting, time: {DateTime.Now}"); } catch (Exception ex) { _logger.LogError($"Failed to excute background processing method, exception:{ex}"); } } } public override async Task StopAsync(CancellationToken stoppingToken) { _logger.LogInformation("Queued Hosted Service is stopping."); await base.StopAsync(stoppingToken); } }

public class MonitorLoop { private readonly IBackgroundTaskQueue _taskQueue; private readonly ILogger _logger; private readonly CancellationToken _cancellationToken; public MonitorLoop(IBackgroundTaskQueue taskQueue, ILogger<MonitorLoop> logger, IHostApplicationLifetime applicationLifetime) { _taskQueue = taskQueue; _logger = logger; _cancellationToken = applicationLifetime.ApplicationStopping; } public void StartMonitorLoop() { _logger.LogInformation($"MonitorAsync loop is starting"); Task.Run(async () => await MonitorAsync()); } private async ValueTask MonitorAsync() { _logger.LogDebug($"Monitor async method is excuting."); while (!_cancellationToken.IsCancellationRequested) { var keyStroke = Console.ReadKey(); if (keyStroke.Key == ConsoleKey.W) { //Enqueue a background work item await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem); } } } private async ValueTask BuildWorkItem(CancellationToken token) { //simulate three 5-second tasks to complete //for each enqueue work item int delayLoop = 0; var guid = Guid.NewGuid().ToString(); _logger.LogInformation($"Queue background task {guid} is starting."); while(!token.IsCancellationRequested && delayLoop < 3) { try { _logger.LogDebug($"Start to enqueue the workItem to queue."); await Task.Delay(TimeSpan.FromSeconds(5), token); } catch (OperationCanceledException ex) { _logger.LogWarning($"The queue background task: {guid} is cancelled, exception: {ex}."); } delayLoop++; _logger.LogInformation($"Queue background task {guid} is running, delay loop: {delayLoop}"); } if (delayLoop == 3) _logger.LogInformation($"Queue background task : {guid} is complete."); else _logger.LogInformation($"Queue background task : {guid} was cancelled."); } }

public static void Main(string[] args) { var builder = CreateHostBuilder(args).Build(); var monitorloop = builder.Services.GetRequiredService<MonitorLoop>(); monitorloop.StartMonitorLoop(); builder.Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { //services.AddHostedService<Worker>(); //services.AddScoped<IScopeProcessingService, ScopedProcessingService>(); //services.AddHostedService<ConsumeScopedServiceHostedService>(); services.AddSingleton<MonitorLoop>(); services.AddHostedService<QueuedHostedService>(); services.AddSingleton<IBackgroundTaskQueue>(ctx => { if (!int.TryParse(hostContext.Configuration["queueCapacity"], out var queueCapacity)) queueCapacity = 100; return new BackgroundTaskQueue(queueCapacity); }); });
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示