用 dotnet new worker -n mybackgroundservice 创建后台服务项目,它默认继承 backgroundservice 并通过 addhostedservice 注册,确保 startasync/stopasync 正确调用;访问 dbcontext 需在 executeasync 内用 iservicescopefactory 创建新作用域;调试用 --console,部署用 sc create 且注意权限与路径。

直接用 dotnet new worker 创建,别碰旧版“Windows Service (.NET Framework)”模板——它在 .NET 6+ 中已彻底失效,硬上只会卡在启动失败或事件查看器报错 1053。
怎么新建一个可运行的后台服务项目
命令行最干净:dotnet new worker -n MyBackgroundService。生成的项目默认继承 BackgroundService,并注册为 IHostedService,开箱即用。
Visual Studio 里新建项目时,选「Worker Service」,语言 C#,框架必须是 .NET 6 或更高(如 .NET 8)。别选“Windows Service (.NET Framework)”——那个模板生成的是 ServiceBase 类,和现代 DI 宿主模型不兼容,编译能过,运行必挂。
项目结构里关键文件是 Worker.cs:它已继承 BackgroundService,你只需重写 ExecuteAsync(CancellationToken stoppingToken) 方法写任务逻辑。
为什么必须用 AddHostedService 而不是 AddSingleton
services.AddHostedService<mybackgroundservice>()</mybackgroundservice> 是唯一正确注册方式。用 AddSingleton<mybackgroundservice>()</mybackgroundservice> 或 AddScoped 会导致 StartAsync 根本不被调用,且无明确报错——日志里只有一句模糊的 “Failed to instantiate hosted service”,容易漏看。
原因在于:只有 AddHostedService 才会把服务加入主机的托管服务列表,并在启动/停止阶段统一调度。Singleton 注册只是把它放进 DI 容器,系统压根不知道它该何时启停。
如果你手动实现 IHostedService(不继承 BackgroundService),两个方法 StartAsync 和 StopAsync 都不能留空或抛异常,哪怕只写 await Task.CompletedTask; 也得有。
如何在后台任务里安全访问 DbContext 等 Scoped 服务
IHostedService 实例本身是 Singleton 生命周期,无法直接注入 DbContext 这类 Scoped 服务,否则启动时会炸出 “Cannot resolve scoped service from root provider” 错误。
正确做法是在 ExecuteAsync 内部用 IServiceScopeFactory 每次创建新作用域:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<mydbcontext>();
// 执行查询或保存
await context.MyEntities.ToListAsync(stoppingToken);
<pre class="brush:php;toolbar:false;"> await Task.Delay(1000, stoppingToken);
}
}
要点:
- 每次数据库操作都新建
scope,用完即释放,绝不缓存DbContext实例 - 不要在
ExecuteAsync外部捕获异常并吞掉——BackgroundService基类已自动记录未处理异常;自己 try/catch 后不 rethrow 会导致服务静默失败 - .NET 6+ 支持直接注入
IServiceProvider替代IServiceScopeFactory,但CreateScope()的开销仍需实测评估
调试和部署时最容易踩的坑
开发阶段别直接双击 exe 启动——Windows 服务进程由 SCM 管理,不走 Main 方法。用 --console 参数以控制台模式跑:dotnet run --project MyBackgroundService.csproj -- --console,这样能看见日志、打断点、热重载。
发布后部署为 Windows 服务,必须用管理员权限执行 sc create:
sc create "MyBackgroundSvc" binPath= "C:\path\to\MyApp.exe" start= auto
注意:binPath= 后面必须有一个空格;路径含空格时整个路径要加英文双引号;start= auto 表示开机自启,也可用 start= demand 手动启动。
服务默认运行在 NT AUTHORITY\LocalService 上下文,没权限写桌面、文档目录或访问网络共享。日志务必写到 EventLog(注册 AddEventLog())或 C:\Windows\Temp 这类公共路径;配置文件读取要用 AppContext.BaseDirectory 构造绝对路径,别依赖相对路径。
真正麻烦的从来不是写逻辑,而是服务账户权限、路径上下文、作用域生命周期这三块——它们不出错时一切安静,一出错就毫无征兆地静默退出或超时失败。











