.NET 6+ 推荐用 WorkerService 模板 + sc.exe 注册,因其基于 IHostedService,天然支持 DI、配置和日志,开发调试更可控;ServiceBase 仅适用于必须依赖 .NET Framework 和 Win32 API 的老项目。

直接上结论:.NET 6+ 项目别碰 ServiceBase,用 WorkerService 模板 + sc.exe 注册;老项目必须跑在 .NET Framework 上、又依赖 Win32 API 或特定服务账户交互的,才考虑 ServiceBase。
为什么 WorkerService 是默认推荐?
它本质是封装了 IHostedService 的长期运行后台程序,生命周期由 StartAsync/StopAsync 控制,天然支持 DI、配置绑定、日志注入,不用手动处理 SCM 调用时机。而 ServiceBase 是 Windows 原生服务模型的托管包装,OnStart/OnStop 里不能阻塞、不能弹窗、不能访问桌面会话 —— 这些限制在 WorkerService 里依然存在,但开发体验更现代、调试更可控。
常见错误现象:
-
sc create成功但服务启动失败,状态卡在“启动中” → 实际是StartAsync里写了同步 IO 或死循环,没及时返回 - 服务启动后读不到
appsettings.json→ 当前工作目录是C:\Windows\System32,不是 exe 所在目录,必须用AppContext.BaseDirectory构造路径 - 日志写入失败或被静默丢弃 → 默认只输出到控制台,服务模式下需显式配置
EventLog或第三方 provider(如 Serilog)
sc.exe 注册时 binPath 和 start 参数怎么填?
sc.exe 是唯一稳定可靠的注册方式,InstallUtil.exe 已过时且在 .NET 6+ 中不兼容。注册命令必须以管理员权限运行:
sc create "MySvc" binPath= "C:\myapp\MyApp.exe" start= auto obj= "NT AUTHORITY\NetworkService"
关键细节:
-
binPath=后面**必须有一个空格**,否则 Windows 会解析失败 - 路径含空格时,
binPath值要用英文双引号包裹,如binPath= "C:\Program Files\MyApp\MyApp.exe" -
obj参数决定服务运行身份:NT AUTHORITY\NetworkService免密码但无本地磁盘写权限;.\<username></username>需配password=,且该账户必须有“作为服务登录”权限(用secpol.msc添加) -
start= demand表示手动启动,start= auto表示开机自启;别用start= delayed-auto,它不被所有 Windows 版本支持
发布时 self-contained 和 runtime identifier 怎么选?
dotnet publish 输出的只是普通 exe,不会自动变成服务。必须发布为自包含(self-contained),否则目标机没装对应 .NET 运行时就直接报错退出:
dotnet publish -c Release -r win-x64 --self-contained true
注意点:
-
-r win-x64必须明确指定,不能省略;win-arm64或win-x86要按目标系统匹配 -
--self-contained true是布尔值,不能写成--self-contained(后者会被忽略) - 发布后检查输出目录是否包含
hostfxr.dll和大量Microsoft.*.dll—— 没这些说明没真正 self-contained - 别在开发机用
dotnet run测试服务行为,它不走 SCM 生命周期;加--console参数才能模拟服务启动流程
调试和日志最容易被忽略的三个地方
服务进程没有控制台窗口,Console.WriteLine 不可见,异常也不抛到桌面。最常踩的坑不是逻辑写错,而是环境假设错了:
- 路径硬编码 → 所有文件操作必须基于
AppContext.BaseDirectory,比如Path.Combine(AppContext.BaseDirectory, "config.json") - 数据库连接字符串写死 localhost → 服务以
NetworkService身份运行时,默认无法访问命名管道,SQL Server 连接要改用 TCP 协议或显式授权 - 定时任务用
System.Timers.Timer但没设AutoReset = true→ 第一次触发后就停了,看起来像“服务没在跑”
真正上线前,至少要在本地用 sc start 启动一次,然后看 Windows 事件查看器 → Windows 日志 → 应用程序,筛选来源为 .NET Runtime 或你的服务名,确认有没有静默崩溃。


















