Windows服务必须继承ServiceBase,不能直接运行控制台逻辑;需重写OnStart/OnStop,任务放后台线程;安装须用sc create或InstallUtil;日志须用EventLog或文件;.NET 6+推荐Worker Service + UseWindowsService。

Windows服务程序必须用 ServiceBase 继承,不能直接跑控制台逻辑
Windows服务不是普通exe,系统启动时不会弹窗、不加载用户会话,所有UI交互和依赖桌面环境的代码都会失败。你写完Main方法直接调用定时器或循环,服务能安装但启动就报错——常见现象是事件查看器里出现Service failed to start due to timeout (30000ms)。
正确做法是让主类继承ServiceBase,重写OnStart和OnStop,把实际任务逻辑放到后台线程或托管服务中运行:
public partial class MyWinService : ServiceBase
{
private Timer _timer;
<pre class='brush:php;toolbar:false;'>public MyWinService()
{
ServiceName = "MyBackgroundTask";
CanStop = true;
CanPauseAndContinue = false;
}
protected override void OnStart(string[] args)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
}
protected override void OnStop()
{
_timer?.Dispose();
}
private void DoWork(object state) { /* 实际任务 */ }}
-
OnStart里只能做轻量初始化,不能阻塞;耗时操作必须异步或另起线程 - 不要在
OnStart里直接调用Thread.Sleep或同步IO,否则超时失败 - 调试阶段建议先写成控制台程序验证逻辑,再套一层
ServiceBase
安装服务必须用 sc create 或 InstallUtil.exe,.NET 6+ 默认不带安装器
.NET Core / .NET 5+ 项目默认不生成ProjectInstaller组件,双击exe不会弹出安装界面,也不能靠“以管理员身份运行”自动注册。常见错误是把服务exe复制到系统目录后手动启动,结果提示Error 1053: The service did not respond to the start or control request in a timely fashion——本质是根本没注册进SCM(服务控制管理器)。
推荐用sc create命令,干净可控:
sc create "MyBackgroundTask" binPath= "C:path oMyWinService.exe" start= auto obj= "NT AUTHORITYLocalService"
-
binPath=后面必须有空格,且路径含空格时要用英文双引号包裹整个路径 -
obj=指定运行账户:LocalService权限最低,适合只访问本地资源;需要网络或文件共享时改用NetworkService - 如果用
InstallUtil.exe,得先装.NET Framework运行时(即使你用的是.NET 6),且要匹配位数(x64项目不能用x86版InstallUtil)
日志不能写到Console.WriteLine或Debug.WriteLine,必须用EventLog或文件
服务没有标准输出流,所有Console.WriteLine、Debug.WriteLine、甚至未捕获异常的堆栈都不会显示。你以为任务在跑,其实早崩了,但什么痕迹都没留下。最典型的表现是服务状态显示“正在运行”,但业务逻辑完全没触发。
必须显式记录日志:
- 用
EventLog写入Windows事件查看器(推荐用于错误和关键状态):EventLog.WriteEntry("MyBackgroundTask", "Started successfully", EventLogEntryType.Information) - 写文件日志时注意权限:用
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)这类系统路径,别硬写C: emp - 避免在
OnStart里直接File.WriteAllText——首次写入可能因目录不存在而抛异常,要先Directory.CreateDirectory
.NET 6+ 推荐用 Worker Service 模板 + Topshelf 或原生 Windows 服务宿主
传统ServiceBase写法在.NET 6+里依然可用,但生命周期管理、配置注入、日志集成都得自己补。更省心的方式是用Worker Service模板,它本质是带IHost的长期运行程序,可通过Microsoft.Extensions.Hosting.WindowsServices包直接注册为Windows服务。
只需两步:
- 在
Program.cs里加.UseWindowsService():Host.CreateDefaultBuilder(args).UseWindowsService().ConfigureServices(...) - 发布时选
win-x64RID,生成单文件exe后用sc create注册 - 别信某些教程说“只要加一行
UseWindowsService就能当服务跑”——它只是启用服务宿主能力,安装、账户、恢复策略仍需手动配
真正容易被忽略的是服务账户的磁盘权限和网络访问限制。哪怕代码100%正确,用LocalService去读\fileservershare也会静默失败。上线前务必用服务账户手动测试关键路径是否可达。








