C产品如何满足特定用户需求?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1032个文字,预计阅读时间需要5分钟。
Windows服务并非能跑就行的控制台程序,安装后默认处于启动中状态,日志未写入文件、调试时双击直接报错等问题,这些都是因为未正确配置对操作系统节点的访问权限。
OnStart 必须秒返回,否则服务启动必然超时
SCM(服务控制管理器)只给 OnStart 最多 30 秒,超时就标记为“启动失败”。很多人把数据库连接、配置加载、HTTP 调用全塞进 OnStart,结果服务永远停在“启动中”。
-
OnStart里只做轻量初始化:注册事件、创建Timer、启动Task.Run或BackgroundService实例 - 禁止同步阻塞:删掉
Thread.Sleep、File.ReadAllText(未加异常处理)、HttpClient.Send等耗时调用 - 异步逻辑别用
async void—— SCM 不 await 它,线程可能被回收;改用Task.Run(() => { ... })或托管到BackgroundService - 日志第一行建议立刻写入 EventLog:
EventLog.WriteEntry("MyService", "OnStart entered", EventLogEntryType.Information),确认入口确实被调用
路径和当前目录全是陷阱,AppContext.BaseDirectory 是唯一可靠起点
服务启动后 Environment.CurrentDirectory 固定是 C:\Windows\System32,不是你的 exe 所在目录。所有相对路径都会失效,appsettings.json、log.txt、config.xml 全读不到。
- 一律用
Path.Combine(AppContext.BaseDirectory, "appsettings.json")构造路径 - 别信
Assembly.GetExecutingAssembly().Location—— 在单文件发布或 ReadyToRun 模式下可能指向临时目录 - 写日志到文件前先检查目录权限:
Directory.CreateDirectory(Path.GetDirectoryName(logPath)),再尝试写入 - 如果必须写用户目录(比如读某个用户的 SQLite DB),不要硬切
HKEY_CURRENT_USER—— 服务没“当前用户”,得显式用ImpersonateLoggedOnUser或换账户运行
.NET 6+ 推荐用 Worker Service + WindowsServices 包,别碰 InstallUtil.exe
InstallUtil.exe 已过时,.NET Core / .NET 5+ 项目默认不带它,硬拷老版本会导致注册表项缺失、服务无法启动,且不支持现代宿主模型。
- 新建项目选 “Worker Service” 模板,安装 NuGet 包:
Microsoft.Extensions.Hosting.WindowsServices - 修改
Program.cs:调用builder.Services.AddWindowsService()并设置options.ServiceName - 启动方式统一为:
Host.CreateDefaultBuilder(args).UseWindowsService().Build().Run() - 安装命令用系统自带
sc:sc create MyService binPath= "C:\path\to\MyService.exe" start= auto obj= "NT AUTHORITY\NetworkService"注意:binPath=后必须有空格,路径必须英文引号包裹,服务名含空格要整体加引号:sc create "My Service"
调试不能双击,也不能靠断点——得模拟服务上下文
双击 exe 会报错 “服务未以服务方式启动”;在 Visual Studio 里按 F5 也会失败,因为没走 SCM 流程。
- 开发阶段加启动参数判断:
if (args.Contains("--debug")) { new MyService().OnStart(null); Console.ReadKey(); return; } - 或用
Debugger.Launch()放在OnStart开头,运行sc start MyService后弹出调试器选择 - 日志别依赖输出窗口:写到
EventLog或磁盘文件,用wevtutil qe Application /q:"*[System[(EventID=1000)]]"查看事件日志 - 卸载前务必先
sc stop MyService,否则sc delete会提示“拒绝访问”
最常被忽略的是 Session 0 隔离和账户权限的组合效应:NetworkService 账户不能写本地磁盘,LocalSystem 不能访问网络共享,而 User 账户又得填密码——这些不是配置问题,是 Windows 服务的运行契约本身决定的,绕不开,只能提前设计好路径、日志位置和凭据策略。
本文共计1032个文字,预计阅读时间需要5分钟。
Windows服务并非能跑就行的控制台程序,安装后默认处于启动中状态,日志未写入文件、调试时双击直接报错等问题,这些都是因为未正确配置对操作系统节点的访问权限。
OnStart 必须秒返回,否则服务启动必然超时
SCM(服务控制管理器)只给 OnStart 最多 30 秒,超时就标记为“启动失败”。很多人把数据库连接、配置加载、HTTP 调用全塞进 OnStart,结果服务永远停在“启动中”。
-
OnStart里只做轻量初始化:注册事件、创建Timer、启动Task.Run或BackgroundService实例 - 禁止同步阻塞:删掉
Thread.Sleep、File.ReadAllText(未加异常处理)、HttpClient.Send等耗时调用 - 异步逻辑别用
async void—— SCM 不 await 它,线程可能被回收;改用Task.Run(() => { ... })或托管到BackgroundService - 日志第一行建议立刻写入 EventLog:
EventLog.WriteEntry("MyService", "OnStart entered", EventLogEntryType.Information),确认入口确实被调用
路径和当前目录全是陷阱,AppContext.BaseDirectory 是唯一可靠起点
服务启动后 Environment.CurrentDirectory 固定是 C:\Windows\System32,不是你的 exe 所在目录。所有相对路径都会失效,appsettings.json、log.txt、config.xml 全读不到。
- 一律用
Path.Combine(AppContext.BaseDirectory, "appsettings.json")构造路径 - 别信
Assembly.GetExecutingAssembly().Location—— 在单文件发布或 ReadyToRun 模式下可能指向临时目录 - 写日志到文件前先检查目录权限:
Directory.CreateDirectory(Path.GetDirectoryName(logPath)),再尝试写入 - 如果必须写用户目录(比如读某个用户的 SQLite DB),不要硬切
HKEY_CURRENT_USER—— 服务没“当前用户”,得显式用ImpersonateLoggedOnUser或换账户运行
.NET 6+ 推荐用 Worker Service + WindowsServices 包,别碰 InstallUtil.exe
InstallUtil.exe 已过时,.NET Core / .NET 5+ 项目默认不带它,硬拷老版本会导致注册表项缺失、服务无法启动,且不支持现代宿主模型。
- 新建项目选 “Worker Service” 模板,安装 NuGet 包:
Microsoft.Extensions.Hosting.WindowsServices - 修改
Program.cs:调用builder.Services.AddWindowsService()并设置options.ServiceName - 启动方式统一为:
Host.CreateDefaultBuilder(args).UseWindowsService().Build().Run() - 安装命令用系统自带
sc:sc create MyService binPath= "C:\path\to\MyService.exe" start= auto obj= "NT AUTHORITY\NetworkService"注意:binPath=后必须有空格,路径必须英文引号包裹,服务名含空格要整体加引号:sc create "My Service"
调试不能双击,也不能靠断点——得模拟服务上下文
双击 exe 会报错 “服务未以服务方式启动”;在 Visual Studio 里按 F5 也会失败,因为没走 SCM 流程。
- 开发阶段加启动参数判断:
if (args.Contains("--debug")) { new MyService().OnStart(null); Console.ReadKey(); return; } - 或用
Debugger.Launch()放在OnStart开头,运行sc start MyService后弹出调试器选择 - 日志别依赖输出窗口:写到
EventLog或磁盘文件,用wevtutil qe Application /q:"*[System[(EventID=1000)]]"查看事件日志 - 卸载前务必先
sc stop MyService,否则sc delete会提示“拒绝访问”
最常被忽略的是 Session 0 隔离和账户权限的组合效应:NetworkService 账户不能写本地磁盘,LocalSystem 不能访问网络共享,而 User 账户又得填密码——这些不是配置问题,是 Windows 服务的运行契约本身决定的,绕不开,只能提前设计好路径、日志位置和凭据策略。

