后台服务到底解决什么问题

有些任务不需要界面,也不该绑在某个登录用户身上:定时清理临时文件、监听目录并转发、持续采集本机性能指标、对接硬件外设。把它们做成普通控制台程序让用户手动开着,人一注销进程就没了;做成 Windows 服务则可以随系统开机自启、在没有任何人登录时运行、崩溃后自动重启。

服务的本质是「无人值守」:你不在电脑前,它也得稳稳地跑。
服务开发的核心动作
  • 生命周期实现启动、停止、暂停与卸载的标准钩子。
  • 配置把可变参数放进注册表或配置文件,而不是写死。
  • 监听用文件监视器、定时器或消息循环驱动业务。
  • 可观测写结构化日志,服务崩溃也能复盘。
Windows 服务实战配图:中央盾牌内嵌齿轮,右侧文件监控折线与文件夹
服务就是系统的守门人:长期驻留、安静监听、按需响应。

从控制台程序到 Windows 服务

直接继承 ServiceBase 写服务,调试体验很差——F5 启动会被当成服务拉起,看不到控制台输出。更顺手的做法是用 Topshelf:业务逻辑写成普通控制台程序,再由它在启动时判断「我是被服务管理器拉起的,还是开发者在调试」。

HostFactory.Run(host =>
{
    host.Service<Worker>(s =>
    {
        s.ConstructUsing(() => new Worker());
        s.WhenStarted(w => w.Start());
        s.WhenStopped(w => w.Stop());
    });
    host.RunAsLocalSystem();
    host.SetServiceName("DataCollector");
    host.SetDisplayName("数据采集服务");
});

这样在开发阶段它就是个控制台,能直接 Console.WriteLine 排错;部署时用 install 参数注册成服务,start 参数启动,一行命令完成。相比手写安装程序,这条路径在中小型项目里性价比最高。

注册表读写与配置落地

服务没有用户界面,配置只能来自文件或注册表。注册表适合放少量、需要权限隔离的设置——例如服务的运行参数、上次处理到的位置。读写要走 Microsoft.Win32 命名空间,并养成「先读默认值、写回时捕获异常」的习惯。

using var key = Registry.LocalMachine.OpenSubKey(
    @"SOFTWARE\DataCollector", writable: true)
    ?? Registry.LocalMachine.CreateSubKey(@"SOFTWARE\DataCollector");

var interval = (int)(key.GetValue("IntervalSec", 30) ?? 30);
key.SetValue("LastRunTick", DateTime.UtcNow.Ticks);

注意一个权限细节:写到 HKLM 需要管理员权限,而服务以 LocalSystem 运行时天然拥有;但如果调试时用普通用户身份跑控制台程序,写 HKLM 会失败。更稳妥的做法是把可变配置放在 HKLM\SOFTWARE 下自己的子键里,并在卸载时清理,避免注册表残留。

文件系统监控:FileSystemWatcher

服务最常见的职责之一是「某个目录一有新文件就处理」。FileSystemWatcher 能订阅文件的创建、修改、删除与重命名事件。但它有两个著名的坑:事件可能在极短时间内爆发(复制大文件会触发几十次 Changed),缓冲区满了还会丢事件。

监视器的正确姿势
  • 防抖事件里不直接处理,先入队列,延迟一小段时间再取。
  • 扩容把 InternalBufferSize 调到 64KB 以上减少丢事件。
  • 筛选用 NotifyFilters 只订阅关心的变化类型。
  • 容错处理文件前先尝试独占打开,占用中就稍后重试。

安装、启动账户与故障恢复

服务的运行账户决定了它能访问什么。LocalSystem 权限最大,但不适合需要访问网络共享的场景;要读 UNC 路径或数据库,应当建一个专用的低权限域账户,把「登录为服务」权限授予它,而不是把服务塞进最高权限账户。

sc.exe create DataCollector binPath= "C:\apps\collector\collector.exe"
sc.exe config DataCollector obj= "DOMAIN\svc_collector" password= "******"
sc.exe failure DataCollector reset= 86400 actions= restart/5000/restart/5000/restart/5000

最后一行设置的「故障恢复」很关键:服务崩溃后 5 秒自动重启,一天(86400 秒)内计数重置。无人值守的意义就在这里——你不必半夜爬起来重启服务,系统自己会把它拉起来。