拓冰建站拓冰建站
首页 / 资讯中心 / 正文

.NET5 Windows服务开发实战:Worker Service全流程指南

简介在服务端开发中后台定时任务与常驻进程的稳定托管是工程化落地的关键一环。Windows 服务作为系统级后台任务载体由服务控制管理器统一管理具备随系统启动、与用户会话无关、支持自动恢复等特性非常适合运维自动化、批量数据处理、定时清理等无人值守场景。基于 .NET5 的 Worker Service 模式通过 Host 构建通用宿主结合依赖注入、配置系统和日志抽象大幅简化了服务开发流程。开发者只需关注业务逻辑即可快速构建可安装、可调试、可长期运行的 Windows 服务。本文从一个可运行示例工程出发系统梳理服务选型、项目搭建、核心编码、发布部署及常见排错方法帮助读者从零掌握 Windows 服务的完整落地路径。 先说结论这份 dotnet5-winservice-demo 是一个基于 .NET5 构建 Windows 服务的完整可运行示例工程解决的是“把后台定时任务以 Windows 服务方式稳定托管”的需求。它涵盖从项目创建、依赖注入、配置管理、日志记录到安装、卸载、调试、排错的全链路内容。适合刚接触 .NET 服务端编程、需要做自动化运维任务、或者想把传统控制台程序转成系统服务的开发者参考。我用这套方案真实落地过一个批量数据处理服务从开发到部署大概花了一个周末后续在线上稳定跑了几个月没出过问题。所以这篇不是堆代码而是把从选型到排查的完整思路捋给你里面很多细节是报错文档里翻不到的那种。1. 项目整体设计与需求拆解1.1 为什么要用 Windows 服务来托管后台任务以前很多人写定时任务都是写一个控制台程序丢到服务器上双击运行或者塞进计划任务。短时间没问题但一旦遇到服务器重启、用户注销、程序异常退出任务就悄悄没了。控制台窗口在租用的云服务器上尤其不靠谱你人不在窗口一关服务就没了。更麻烦的是如果你的任务需要开机就运行控制台方案基本做不到。Windows 服务就解决了这个场景。它由系统服务控制管理器SCM统一管理随着系统启动而启动与用户登录状态无关而且有统一的状态管理、启动参数、错误恢复策略还能在“服务”面板里一键操作。对于任何需要长期跑、无人值守的后台任务Windows 服务都是最稳妥的载体。这个 Demo 的核心定位就是给你一个可以直接抄作业的骨架我帮你把服务宿主、配置读取、日志、定时循环、安装脚本都准备好你只需要往 ExecuteAsync 里填自己的业务逻辑就行。这也是我整理这个 Demo 的出发点——新手最大的痛点不是不会写业务而是不知道一个能上线跑的服务应该长什么样。1.2 .NET5 在这个场景里的优势选择 .NET5 不是拍脑袋。在 .NET Core 3.0 之前写 Windows 服务的主要路线是 System.ServiceProcess.ServiceBase那个体验相当原始没有依赖注入、没有配置系统、没有日志抽象想优雅停机还得自己处理一堆 SCM 状态。到了 .NET Core 3.0微软引入了 Microsoft.Extensions.Hosting.WindowsServices提供了 UseWindowsService 扩展把 Worker Service 和 Windows 服务生命周期整合在一起。.NET5 作为 .NET Core 3.0 之后的第一个大一统版本补全了 Hosting 体系的最后一块拼图。你可以在项目里放心使用依赖注入DI、IConfiguration 配置系统、ILogger 日志抽象甚至能像写 ASP.NET Core 那样组织后台任务代码。服务运行时的托管问题随系统启动、停止通知、失败重启全部交给 UseWindowsService 处理开发者只关心业务本身这套分工带来的开发效率是明显的。另外.NET5 虽然是较早的版本现在官方支持期已经结束但框架本身的稳定性和生态兼容性已经过大规模验证。如果你在生产环境使用我建议直接上 .NET6/.NET8因为 Demo 的核心代码结构几乎不用改只需要把 TFM 从 net5.0 改成 net6.0 或 net8.0 就行。1.3 Demo 里我放了哪些东西解压 dotnet5-winservice-demo.zip 之后你会看到这样一个结构解决方案文件DemoWinService.sln服务主工程DemoWinService.csproj核心源码Program.cs、Worker.cs配置文件appsettings.json发布与运维脚本install.bat、uninstall.bat、start.bat、stop.bat说明文档README.md以下是核心代码文件清单文件作用Program.cs服务宿主入口注册 Windows 服务、DI 容器和配置Worker.cs后台任务主体定时执行业务逻辑appsettings.json配置服务名、执行间隔、开关等运行参数install.bat以管理员身份安装并启动服务uninstall.bat停止并删除服务麻雀虽小五脏俱全。这个结构基本就是“最小可运行 Windows 服务”的标准模板后面的章节我会逐个文件拆开讲。2. 技术路线选型从 ServiceBase 到 Worker Service2.1 三条路线对比我见过有人写 Windows 服务第一反应去查 System.ServiceProcess.ServiceBase 的旧教程也有人直接用 Topshelf还有人干脆用 nssm 把任意 exe 包成服务。不能说哪个绝对错但选型之前要看清楚各自代价。下面这个表我压箱底用了很久方案优点缺点适用场景ServiceBase 原生开发官方原生、无第三方依赖模板代码多、手动处理安装注册、无 DI/配置/日志抽象极简单服务、维护老项目Topshelf开发调试友好、控制台可以直接运行、安装命令简单第三方库维护节奏看社区在 .NET 5 时代更新偏慢快速交付的小型服务、团队已用熟Worker Service UseWindowsService依赖注入、配置系统、日志抽象齐全代码量少微软官方支持对 .NET 版本有要求3.0新项目首选尤其是需要长期维护的服务用老方案写一个服务光是让“服务能启动并正确响应停止命令”就要写一堆 OnStart、OnStop、ServiceBase.Run 之类的样板代码。而 Worker Service 把这些全藏起来了你只要关心任务本身。2.2 为什么最终选择 Worker Service 方案我选择 Worker Service 的核心原因是前后端技术栈统一。如果你写过 ASP.NET Core再看 Worker Service 的 Program.cs 会非常眼熟都是 Host.CreateDefaultBuilder 那套。依赖注入、配置绑定、日志级别过滤、环境区分全都有现成接口需要引入第三方组件时比如读取数据库、调用 HTTP API直接在 ConfigureServices 里注册服务不用像老方案那样手撸一个微型容器。更重要的是这套方案天然支持优雅停机。SCM 向服务发送停止指令后UseWindowsService 会触发 IHostedService 的 StopAsync那个用于取消后台任务的 CancellationToken 会变成取消状态你的 ExecuteAsync 就能捕获到把任务干净利落地收尾。这在处理数据库连接、消息队列等需要释放资源的场景下非常关键。2.3 什么情况下还考虑 TopshelfTopshelf 也不是没有价值。它最大的优势是安装命令特别简单Topshelf.HostFactory.Run 自带 Install、Uninstall、Debug 等命令开发者直接敲 exe install 就能装服务体验比 sc create 友好很多。如果你的服务逻辑很简单、又不想引入完整的 Hosting 体系用 Topshelf 可以快速交付。但要注意 Topshelf 的维护节奏在近几年明显放缓在 .NET 5 的新项目里使用需要谨慎评估。我个人只会在老项目维护时保留 Topshelf新项目一律走 Worker Service。这不是说 Topshelf 不好而是面向未来的取舍。3. 开发环境与工程搭建3.1 开发环境要求这个 Demo 的运行环境要求不复杂Windows 10 或 Windows Server 2016用于实际安装服务Visual Studio 2019 16.8 或者 Visual Studio 2022也可以用 dotnet CLI.NET 5 SDK如果你只是看代码装 SDK 就够了有人会问Windows 服务能不能在 Linux 上开发能但发布和验证还得回到 Windows 机器上因为服务的安装、启动、停止依赖 Windows 的 SCM 机制。我在开发阶段会用一台 Windows 虚拟机专门做服务验证主开发机写代码两边同步很快。3.2 创建项目的三种方式第一种是 Visual Studio 图形化操作新建项目找到“工作线程服务Worker Service”模板创建完成后修改 csproj 里的 TFM同时安装 NuGet 包 Microsoft.Extensions.Hosting.WindowsServices。第二种是 dotnet CLI一条命令搞定适合习惯命令行的开发者dotnet new worker -n DemoWinService cd DemoWinService dotnet add package Microsoft.Extensions.Hosting.WindowsServices第三种最省事直接解压我提供的 dotnet5-winservice-demo.zip把 DemoWinService.csproj 用 Visual Studio 打开就能跑。这是最快上手的路径适合想先跑起来再说的人。打开项目后第一件事是把 csproj 里的 TargetFramework 改成 net5.0并且确认包含 UseWindowsService 的包引用。如果不引用这个包编译没问题但服务没法真正注册到 Windows 服务管理器。初学者最容易漏掉这一步。3.3 工程目录的职责划分这个 Demo 只有四个核心文件但每个文件的职责我非常克制地分开Program.cs 只负责“服务的启动和组装”Worker.cs 只负责“业务循环”appsettings.json 只负责“参数配置”bat 脚本只负责“部署运维”。这个边界的价值是将来你往里面加业务模块时不用在启动文件里堆业务逻辑代码的可维护性会好很多。很多新手喜欢把配置写死在代码里比如“每 60 秒执行一次”就写个常量。我建议一开始就把这类参数放进 appsettings.json哪怕只有一个参数。因为服务一旦装到服务器上改配置比重编译再发布要快得多这也是 Demo 里留出 Service:IntervalSeconds 的原因。4. 核心代码实现与原理拆解4.1 Program.cs服务宿主是怎么“立”起来的先看完整代码using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace DemoWinService { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) Host.CreateDefaultBuilder(args) .UseWindowsService(options { options.ServiceName Dotnet5WinServiceDemo; }) .ConfigureServices((hostContext, services) { services.AddHostedServiceWorker(); }); } }这段代码做了四件事。第一Host.CreateDefaultBuilder(args) 是入口。它会自动加载当前目录下的 appsettings.json、环境变量、命令行参数还自动配置好了控制台日志和调试工具省去了一堆手工初化。第二UseWindowsService(options ...) 是关键。它会告诉宿主“当前进程运行在 Windows 服务模式下”并把你设定的 ServiceName 注册给 SCM。这里不需要写服务安装代码安装操作是靠外部命令sc 或 PowerShell完成的ServiceName 要和安装时保持一致否则启动会报找不到服务的错。第三ConfigureServices 里 services.AddHostedService () 把后台任务注册进依赖注入容器。之后容器会自动创建 Worker 实例并调用它的 StartAsync。因为容器是自带依赖注入的Worker 构造函数里想注入 ILogger、IConfiguration 都可以不需要手动 new。第四Build().Run() 启动宿主并进入阻塞状态直到进程被系统停止。注意这里不是简单跑一个方法而是把服务的生命周期绑定到了 SCM。有人会问为什么不用 AddSystemd因为 Systemd 是 Linux 服务的机制Windows 服务使用 UseWindowsService如果两个平台都要支持可以写成条件编译但 Demo 只聚焦 Windows所以只保留 Windows 服务逻辑。4.2 Worker.cs后台任务循环的设计Worker 继承自 BackgroundService核心逻辑在 ExecuteAsync 里using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DemoWinService { public class Worker : BackgroundService { private readonly ILoggerWorker _logger; private readonly IConfiguration _config; public Worker(ILoggerWorker logger, IConfiguration config) { _logger logger; _config config; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { int interval _config.GetValueint(Service:IntervalSeconds, 60); _logger.LogInformation(服务已启动执行间隔{interval} 秒, interval); while (!stoppingToken.IsCancellationRequested) { try { // 这里写你的业务逻辑 _logger.LogInformation(任务执行中当前时间{time}, DateTimeOffset.Now); } catch (Exception ex) { _logger.LogError(ex, 任务执行发生异常); } await Task.Delay(interval * 1000, stoppingToken); } _logger.LogInformation(服务正在停止); } } }这段代码有两处值得细看。第一循环条件使用 stoppingToken.IsCancellationRequested。当服务收到停止命令时这个 token 会被置为取消状态循环会在当前迭代完成后结束。如果业务正在执行一个耗时操作你还可以把 token 传给内部的异步方法让它提前中断做到优雅停机。第二我习惯在循环里包一层 try/catch。原因是服务一旦在后台任务里抛出未捕获异常SCM 可能直接将服务标记为“已停止”而且在事件查看器里很难定位。把异常捕获住并记录日志至少能让服务继续跑你也能通过日志知道哪里出了问题。对于定时任务这种“不能自己挂掉”的场景这层保护非常值得。有人会把复杂业务直接写在 ExecuteAsync 里但更好的做法是抽一个独立服务类在构造函数里注入然后在循环里调用。这样业务逻辑可以单独测试也方便以后把任务拆成多个模块。4.3 配置与日志服务运行时的“眼睛”Demo 的 appsettings.json 是这样设计的{ Service: { ServiceName: Dotnet5WinServiceDemo, IntervalSeconds: 60, EnableTask: true }, Logging: { LogLevel: { Default: Information, Microsoft.Hosting.Lifetime: Information } } }配置项的含义ServiceName服务的显示名称安装脚本会使用这个值修改时要同步改 Program.cs 里 UseWindowsService 的配置。IntervalSeconds任务执行间隔单位是秒不配置时默认 60。EnableTask任务总开关如果你想临时停掉业务逻辑但保留服务运行可以把它设为 false。日志方面内置的 ILogger 默认会输出到控制台和 Windows 事件日志。当服务在后台运行时你看不到控制台输出所以一定要在代码里用 ILogger 记录关键节点尤其是启动、停止、异常这三个节点。事件日志是排查问题的第一信息源没有日志的服务等于盲盒。如果你需要更完整的产品级日志可以引入 Serilog配置一个 RollingFile让日志按天滚动保存。这样即使服务异常退出也有历史记录可查。4.4 接入真实业务逻辑的示例为了让 Demo 不只是“Hello World”我给它加了一个典型场景定时清理系统临时目录和 %TEMP% 下的文件。这个功能虽然简单但很能说明“服务”和“普通批处理”的区别你可以在 bat 里写一段清理命令但一旦放到服务器上怎么保证它每天都运行怎么保证不因为异常而中断怎么记录执行结果把这类逻辑放进服务里执行时间、执行结果、异常日志都能统一管理。我见过有人甚至把“优化 Windows 游戏性能”的批处理比如调整电源模式、清理临时文件改写成后台服务定期执行。这听起来有点怪但也说明服务是一个很灵活的容器核心价值是把“人工定期去跑命令”变成“系统自动帮你跑”。下面我给出一个在 Worker 里调用外部命令的示例try { var process new System.Diagnostics.Process { StartInfo new System.Diagnostics.ProcessStartInfo { FileName cmd.exe, Arguments /c cleanmgr /sagerun:1, CreateNoWindow true, UseShellExecute false } }; process.Start(); await process.WaitForExitAsync(stoppingToken); _logger.LogInformation(清理任务完成退出码{exitCode}, process.ExitCode); } catch (Exception ex) { _logger.LogError(ex, 调用外部清理命令失败); }注意这里用 CreateNoWindow 避免弹出黑窗用 WaitForExitAsync 配合取消令牌服务停止时可以及时释放进程。5. 发布、安装与部署实操5.1 发布选择自包含还是框架依赖代码写好后下一步就是发布。这里有个必须做的决策自包含部署还是框架依赖部署。自包含部署会把整个 .NET 运行时一起打进去发布目录大概几十 MB目标机器上不用预装 .NET 运行时兼容性最好。缺点是体积大、每次更新都要发布完整运行时。框架依赖部署体积小发布目录只有几个 MB但目标机器必须安装对应版本的 .NET 运行时否则服务启动会报“无法加载 DLL”之类的错误。我的建议是如果是内网部署、服务器环境可控优先用框架依赖如果是给客户部署不知道对方环境绝对选择自包含。命令分别如下# 框架依赖 dotnet publish -c Release -o ./publish # 自包含 dotnet publish -c Release -r win-x64 --self-contained true -o ./publish发布成功后publish 目录里会有一个 DemoWinService.exe这就是服务的可执行文件。注意把整个 publish 目录拷贝到服务器不要只拷一个 exe因为依赖的 dll 文件都在旁边。5.2 安装服务的三种方式1sc create。这是最通用的方式适合 Windows 7 到 Windows 11、Windows Server 全系列sc create Dotnet5WinServiceDemo binPath D:\services\publish\DemoWinService.exe start auto这里有两个新手最容易踩的坑第一sc create 的等号后面必须有一个空格写成 binPath 路径 而不是 binPath路径第二binPath 指向的一定是 exe 路径不是 csproj 或 dll 路径。命令执行成功后再用 sc start 启动服务。2PowerShell New-Service。语法更友好适合在 PowerShell 里操作New-Service -Name Dotnet5WinServiceDemo -BinaryPathName D:\services\publish\DemoWinService.exe -StartupType Automatic3installutil。这是 .NET Framework 时代的工具只适用于以 ServiceBase 方式写的服务对 .NET Core/.NET5 服务不适用如果你在网上看到 installutil 教程先确认是不是老项目历史文章。5.3 用 bat 脚本封装安装、卸载和运维我经常和同事说服务管理一定要脚本化别每次都手动敲 sc 命令。这个 Demo 里包含的 install.bat 是核心echo off setlocal enabledelayedexpansion set SERVICE_NAMEDotnet5WinServiceDemo set BIN_PATH%~dp0publish\DemoWinService.exe :: 检查管理员权限 net session nul 21 if %errorlevel% neq 0 ( echo [ERROR] 请以管理员身份运行此脚本。 pause exit /b 1 ) :: 检查服务是否已存在 sc query %SERVICE_NAME% nul 21 if %errorlevel% equ 0 ( echo [INFO] 服务 %SERVICE_NAME% 已存在跳过安装。 ) else ( echo [INFO] 正在创建服务... sc create %SERVICE_NAME% binPath %BIN_PATH% start auto if %errorlevel% neq 0 ( echo [ERROR] 创建服务失败。 pause exit /b 1 ) ) :: 启动服务 echo [INFO] 正在启动服务... sc start %SERVICE_NAME% if %errorlevel% neq 0 ( echo [ERROR] 启动服务失败请查看系统事件日志。 pause exit /b 1 ) echo [OK] 服务安装并启动成功。 pause这段脚本做了三件关键事检查管理员权限、检查服务是否已存在、创建并启动服务。很多服务安装失败都是因为权限不够。脚本里用 net session 检测当前用户是否为管理员不是就直接退出避免执行一半才报权限问题。如果服务名已经被其他程序占用脚本会提示“服务已存在”。常见的占用者是 mysql80、nginx 这类通过服务方式运行的程序。解决办法有两种换一个服务名或者先删除旧服务再安装。删除旧服务命令是sc stop 旧服务名 sc delete 旧服务名对应地uninstall.bat 内容是sc stop Dotnet5WinServiceDemo sc delete Dotnet5WinServiceDemo注意 sc delete 服务名前不需要先停止但稳妥起见还是先 stop 再 delete避免文件被占用。5.4 服务安装后的启动与验证服务安装好之后可以通过“Win R 输入 services.msc”打开服务管理器在列表里找到 Dotnet5WinServiceDemo状态应该是“正在运行”。如果服务启动失败第一步是查看系统事件查看器事件查看器 → Windows 日志 → 应用程序。.NET 运行时抛出的异常、服务状态转换错误都会记录在那里。我在排查 2186 这类错误时80% 的线索都来自事件查看器。你也可以用命令行查询服务状态sc query Dotnet5WinServiceDemo输出里的 STATE 一栏会显示 RUNNING 或 STOPPED配合 win32 退出码能快速判断问题位置。6. 常见问题与排查技巧实录6.1 服务没有响应控制功能net helpmsg 2186这个错误在运维环境里非常常见。完整提示是服务没有响应控制功能。 请键入 net helpmsg 2186 以获得更多的帮助。这句话的实际含义是SCM 启动服务后服务在规定时间内没有向 SCM 报告“我已经启动了”SCM 等不到状态更新就报 2186。常见原因有四个Program.cs 里没有调用 Run或者 Main 方法被某个耗时初始化阻塞住了导致服务宿主一直没起来。服务的启动逻辑里做了耗时长操作比如连接远程数据库、加载大文件、调用外部接口SCM 默认等待超时只有 30 秒超时就会报这个错。服务的可执行文件依赖的 DLL 找不到进程启动失败服务根本没有正常进入运行状态。服务工作目录不对导致相对路径读取 appsettings.json 失败然后异常退出。排查思路很简单先在服务安装目录下直接双击运行 DemoWinService.exe。如果能当控制台程序正常运行说明代码没问题问题多半出在安装路径、权限或 SCM 交互上如果双击就报错那就看命令行窗口里的异常信息。注意Worker Service 默认在控制台模式下也能跑但 UseWindowsService 会让它忽略控制台专用逻辑这样验证不一定完全等价但能快速缩小范围。6.2 服务名被占用怎么办我见过最典型的例子装 MySQL 后服务名 mysql80 被注册后面你给别的程序起名为 mysql80 或者 nginx就会提示“服务名已被占用”。Windows 服务管理器要求服务名唯一物理路径可以相同但服务名不能重复。处理方式有两种。第一种是把当前服务的服务名改成一个不冲突的名字比如 Dotnet5WinServiceDemo同时在 Program.cs 里的 UseWindowsService(options options.ServiceName) 同步修改重新发布安装。第二种是确认旧服务不再需要用 sc delete 旧服务名 删除再装新的。这里有经验提醒服务安装用的名字和 UseWindowsService 里配置的名字一定要保持一致不然 Windows 会新建一个空的注册项启动时找不到正确的宿主入口表现就是“服务已启动但立刻退出”或者 2186。最好把这个名字统一沉淀到 appsettings.json 或一个常量文件里从源头避免不一致。6.3 服务启动后立刻退出服务启动后几秒内就变回停止状态这是新手的经典问题。原因通常是 Main 方法没有真正阻塞住服务宿主没有进入运行状态。比如有人忘记调用 Build().Run()或者手动 new 了一个 service process 就直接返回。解决方法确认 Program.cs 里是 CreateHostBuilder(args).Build().Run()Run 是阻塞的。确认 csproj 里有 Microsoft.Extensions.Hosting.WindowsServices 包引用并且 Program.cs 里调用了 UseWindowsService。打开事件查看器看看有没有未捕获异常。.NET Runtime 的错误日志一般会写清楚异常类型和堆栈。如果服务启动后立刻退出但事件日志什么都没写试试在 Main 外层包一层 try/catch把异常写到文件里这样可以拿到第一手错误信息。6.4 部署目录与权限问题服务默认运行账户是 LocalSystem权限很高但访问某些网络共享目录或特定用户目录时还是会遇到权限问题。我踩过的一个坑是服务写日志到 D:\logs但 D 盘根目录没有给 Everyone 或 Network Service 写权限结果日志一直是空的任务也没执行排查了很久才发现是目录权限。解决办法给服务运行的账户通常是 LocalSystem 或 NetworkService赋予日志目录的写权限。如果是文件夹不存在就在安装脚本里用 mkdir 先创建别依赖程序自动创建因为有些目录结构程序没有权限创建。另一个坑是服务的工作目录默认是 exe 所在目录但如果你用其他工具启动 exe工作目录可能不一样。代码里尽量用绝对路径或者通过 AppContext.BaseDirectory 来拼路径不要用相对路径。6.5 调试 Windows 服务的高效手法Windows 服务调试比普通程序要折腾因为你附加调试器的时候服务已经在 SCM 下运行了。我用得最多的是“控制台模式启动”和“附加进程”两种结合。Worker Service 最方便的地方是它可以像普通控制台程序一样运行。在开发机上直接 F5 运行 DemoWinService.exe任务循环会在前台跑日志直接打在控制台你可以随时打断点、单步调试、改配置后重启。这种方式能覆盖 90% 的业务逻辑问题。只有涉及服务生命周期开机启动、SCM 状态切换的问题才需要装服务再调试。需要调试真实服务进程时用 Visual Studio 的“调试 → 附加到进程”进程类型选择“托管(v4.6)”找到 DemoWinService.exe 附加进去。这时候断点能命中但如果是启动阶段的代码需要在服务启动前提前附加操作上稍微麻烦。更快的方式是在代码里加一句 Debugger.Break()发布前记得删掉。7. 实操笔记与后续扩展7.1 我踩过的几个坑第一个坑是 sc create 的语法错误。我在第一次用 sc create 时把等号写成了 binPathD:\...”结果 sc 提示“参数格式不正确”后来才发现等号后面必须跟一个空格。这个错误在文档里写得很清楚但在命令行里出错了还是会懵一下。第二个坑是自包含发布忘了加 --self-contained true导致拷到服务器后提示缺少运行时。从那以后我所有面向外部环境发布的工程都用自包含模式并且发布完以后检查一下 publish 目录里有没有 DemoWinService.runtimeconfig.json以及有没有一堆 System.*.dll那就是自包含成功的标志。第三个坑是服务日志目录权限。我以为 LocalSystem 账户权限足够结果 Windows 对某些系统盘的根目录写操作也有 UAC 限制日志目录最好提前建好并给 Network Service 写权限。第四个坑是安装脚本里的服务名和 UseWindowsService 里的服务名不一致。我有一次图省事改了安装脚本里的名字忘了改 Program.cs服务装上了但一启动就退出事件日志里也查不到明确原因。后来把所有服务名的引用都统一到一个常量里这个问题彻底解决。7.2 这个 Demo 还能怎么扩展这个骨架的扩展能力很强。你可以加多个 Worker比如一个负责定时清理临时文件一个负责轮询数据库一个负责调用外部 API。只需要在 ConfigureServices 里多次调用 AddHostedService 注册不同的后台任务它们会并行运行互不干扰。每个 Worker 的异常处理、日志、配置都是独立的排查问题也清楚。如果你需要服务间互相通信可以把任务模块注册成单例服务在多个 Worker 间共享状态。比如一个 Worker 负责采集数据另一个 Worker 负责上报采集到的数据放到一个线程安全的队列里上报 Worker 定时取出并发送。依赖注入帮你把这些模块管理好代码结构非常清晰。还可以把服务改造成支持健康检查的版本对外暴露一个健康检查接口由监控系统判断服务是否正常。虽然 Windows 服务不像 Web 服务那么适合对外提供接口但在内网环境用一个轻量级 HTTP 监听器做健康检查完全可行这样服务状态就能纳入监控平台了。最后再分享一个小技巧如果以后要迁移到 Linux这套代码不需要大改只需要把 UseWindowsService 换成 UseSystemd然后重新发布为 Linux 服务。这就是 .NET 生态带来的跨平台红利你只需要维护一份业务代码代价很低。我在实际使用中发现一个稳定的 Windows 服务核心不在代码写得多花哨而在于异常处理、日志和运维脚本这三板斧。把这三样做好了服务跑个一年半载不碰它都不会出问题。这份 Demo 就是按这个思路设计的你拿去填上自己的业务逻辑就能直接上线。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门