IHostedService 与 BackgroundService
实现 IHostedService 和抽象的 BackgroundService,在 .NET 主机中运行后台工作。
IHostedService 与 BackgroundService 是 CoddyKit 上的免费 C# Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
什么是后台服务
后台服务会与您的 ASP.NET Core 应用并行执行长期运行的工作——处理队列、运行计划任务或监控资源。.NET 提供两个接口:IHostedService(最简形式)和抽象的 BackgroundService(适合编写循环)。
IHostedService 接口
IHostedService 有两个方法:StartAsync(主机启动时调用)和 StopAsync(平稳关闭期间调用)。它非常适合执行一次性的启动或关闭任务。
public class DatabaseMigratorService : IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
public DatabaseMigratorService(IServiceScopeFactory sf) => _scopeFactory = sf;
public async Task StartAsync(CancellationToken ct)
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync(ct);
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
builder.Services.AddHostedService<DatabaseMigratorService>();BackgroundService:长时间运行的循环
BackgroundService 是一个实现了 IHostedService 的抽象类,并提供 ExecuteAsync(CancellationToken)——该方法会在应用的整个生命周期内运行。
public class HeartbeatService : BackgroundService
{
private readonly ILogger<HeartbeatService> _logger;
public HeartbeatService(ILogger<HeartbeatService> l) => _logger = l;
protected override async Task ExecuteAsync(CancellationToken ct)
{
_logger.LogInformation("Heartbeat service started");
while (!ct.IsCancellationRequested)
{
_logger.LogInformation("Heartbeat: {Time}", DateTime.UtcNow);
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
_logger.LogInformation("Heartbeat service stopping");
}
}
builder.Services.AddHostedService<HeartbeatService>();使用作用域服务
后台服务是单例。要使用作用域服务(例如 DbContext),请始终使用 IServiceScopeFactory 为每个工作单元创建新的作用域。
public class DataSyncService : BackgroundService
{
private readonly IServiceScopeFactory _factory;
private readonly ILogger<DataSyncService> _log;
public DataSyncService(IServiceScopeFactory f, ILogger<DataSyncService> l)
{ _factory = f; _log = l; }
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
using (var scope = _factory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await SyncDataAsync(db, ct);
} // scope and DbContext disposed here
await Task.Delay(TimeSpan.FromMinutes(5), ct);
}
}
}平稳关闭
主机关闭时会触发取消令牌。您的 ExecuteAsync 应当检查该令牌并执行清理。主机默认最多等待 5 秒,然后才会强制停止。
protected override async Task ExecuteAsync(CancellationToken ct)
{
try
{
while (!ct.IsCancellationRequested)
{
await DoWorkAsync(ct);
await Task.Delay(1000, ct);
}
}
catch (OperationCanceledException)
{
// Normal shutdown — not an error
_logger.LogInformation("Service stopping due to cancellation");
}
finally
{
// Cleanup
await CleanupAsync();
}
}基于通道的后台队列
一种常见模式是:后台服务从通道队列中读取工作项,而 HTTP 端点负责将工作项加入队列。这样可以将请求处理与工作处理解耦。
public class BackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue
= Channel.CreateBounded<Func<CancellationToken, Task>>(100);
public ValueTask QueueAsync(Func<CancellationToken, Task> job)
=> _queue.Writer.WriteAsync(job);
public IAsyncEnumerable<Func<CancellationToken, Task>> DequeueAllAsync(
CancellationToken ct)
=> _queue.Reader.ReadAllAsync(ct);
}
// Register and use:
builder.Services.AddSingleton<BackgroundTaskQueue>();
builder.Services.AddHostedService<QueueProcessorService>();后台服务中的错误处理
ExecuteAsync 中未处理的异常可能会悄然停止服务。请在主循环中使用 try/catch 进行包装并记录错误,以便服务即使遇到单个故障也能继续运行。
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await ProcessNextBatchAsync(ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
break; // normal shutdown
}
catch (Exception ex)
{
_logger.LogError(ex, "Background processing error. Retrying in 5s.");
await Task.Delay(5000, ct); // backoff before retry
}
}
}StopAsync 与关闭超时
重写 StopAsync 来执行清理。主机会传入关闭令牌——如果清理时间超过超时时间,进程仍会被终止。
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Service is stopping...");
// Signal internal work to stop
_internalCts.Cancel();
// Wait for the Execute loop to complete (up to timeout)
await base.StopAsync(stoppingToken);
_logger.LogInformation("Service stopped");
}
// Increase the shutdown timeout if needed:
builder.Services.Configure<HostOptions>(opt =>
opt.ShutdownTimeout = TimeSpan.FromSeconds(30));多个后台服务
可以注册多个托管服务——它们都会并发运行。主机会按注册顺序启动它们,并按相反顺序停止它们。
builder.Services.AddHostedService<DatabaseMigratorService>(); // startup task
builder.Services.AddHostedService<MetricsCollectorService>(); // continuous
builder.Services.AddHostedService<EmailNotificationService>(); // continuous
builder.Services.AddHostedService<CacheWarmupService>(); // startup task
// All run concurrently after app start
// Stopped in reverse registration order on shutdown实际案例:订单处理工作程序
一个从数据库队列中处理待处理订单的后台服务,并正确处理作用域和错误。
public class OrderProcessorService : BackgroundService
{
private readonly IServiceScopeFactory _factory;
private readonly ILogger<OrderProcessorService> _log;
public OrderProcessorService(IServiceScopeFactory f, ILogger<OrderProcessorService> l)
{ _factory = f; _log = l; }
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
using var scope = _factory.CreateScope();
var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();
int count = await processor.ProcessPendingAsync(ct);
_log.LogInformation("Processed {Count} orders", count);
}
catch (Exception ex) when (!ct.IsCancellationRequested)
{
_log.LogError(ex, "Order processing failed");
}
await Task.Delay(TimeSpan.FromSeconds(10), ct);
}
}
}快速检查
为什么后台服务必须使用 IServiceScopeFactory,而不能直接注入 DbContext?
回顾:IHostedService 与 BackgroundService
关键要点:
IHostedService:StartAsync/StopAsync — 用于启动或关闭任务BackgroundService:ExecuteAsync 循环 — 用于持续运行的后台工作- 在后台服务中使用作用域依赖项时,始终使用
IServiceScopeFactory - 在每次异步等待时检查取消令牌,以实现平稳关闭
- 使用 try/catch 包装循环主体,防止服务因错误而终止
- 如果清理需要超过 5 秒,请使用
HostOptions.ShutdownTimeout
常见问题解答
「IHostedService 与 BackgroundService」课时是免费的吗?
是的 — 「IHostedService 与 BackgroundService」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「IHostedService 与 BackgroundService」这节课中我会学到什么?
实现 IHostedService 和抽象的 BackgroundService,在 .NET 主机中运行后台工作。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「IHostedService 与 BackgroundService」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- IHostedService 与 BackgroundService
- Worker Service 项目
- 周期性任务与计时器
- Quartz.NET 计划任务