必须用独立 updater.exe 实现更新:检查版本(version 实例比对)、下载(httpclient+progress)、旧进程退出后复制替换、重启时恢复参数和权限。

直接在主程序里覆盖自己正在运行的 .exe 文件一定会失败。Windows 会锁住该文件,报 System.UnauthorizedAccessException 或 File is being used by another process。必须把更新逻辑拆成四步:检查版本 → 下载包 → 启动独立更新器 → 旧进程退出后替换并重启。
怎么安全地比对版本号
别用字符串直接比较(比如 "1.10" > "1.2" 会返回 false),也别硬编码服务器地址。版本号要解析成 Version 实例再比:
var current = Assembly.GetExecutingAssembly().GetName().Version;var remote = new Version(versionInfo.LatestVersion);- 用
remote > current判断是否需更新,它按主/次/修订号逐级比较 - 版本信息建议从
appsettings.json读取 API 地址,例如"UpdateApiUrl": "https://api.example.com/update/check",避免改代码发版 - 如果服务端返回的是语义化版本(如
v2.3.1-rc),用第三方库SemanticVersioning解析,原生Version不支持预发布标识
下载更新包时如何避免卡死 UI
WinForms 中用 WebClient 已过时,且默认同步阻塞;用 HttpClient + Progress<double></double> 才能实时更新进度条:
- 创建
HttpClient实例(建议单例复用,避免 socket 耗尽) - 调用
GetAsync(url, HttpCompletionOption.ResponseHeadersRead)先拿到响应头,确认Content-Length - 用
response.Content.ReadAsStreamAsync()获取流,再配合Stream.CopyToAsync(fileStream, progress)推进度 - 进度回调里用
InvokeRequired判断线程,只在 UI 线程更新progressBar.Value和提示文本 - 别把 ZIP 直接解压到
Application.StartupPath—— 权限不足时会失败,应先下到Path.GetTempPath()下的子目录
为什么必须用独立的 Updater.exe 而不是 in-process 替换
很多人试过用 Assembly.LoadFrom 或反射加载新 DLL 实现“热更新”,这在 WinForms 桌面应用中基本不可行:
- 窗体实例、静态字段、未释放的资源句柄(如打开的文件、数据库连接)全部残留,新旧逻辑混在一起极易崩溃
- .NET 6+ 默认禁用
LoadFrom,且无法卸载已加载的程序集 - 真正稳定的做法是写一个极简控制台程序
Updater.exe,只做三件事:等主程序退出 → 复制新文件 → 启动新主程序 - 它不引用你主程序的任何 DLL,目标框架设为
net8.0即可,编译后体积可压到 100KB 内 - 关键点:复制前加
Task.Delay(1000),确保旧进程完全释放句柄;复制时用File.Copy(path, dest, true)强制覆盖
重启后怎么恢复原始启动参数和窗口状态
如果主程序只是简单调用 Process.Start("Updater.exe") 就退出,新进程启动时会丢失所有上下文:
- 关闭前必须把
Environment.GetCommandLineArgs()、Environment.CurrentDirectory、甚至窗口位置/大小序列化存到临时文件(如Path.Combine(Path.GetTempPath(), "MyAppUpdate", "state.json")) -
Updater.exe启动主程序时,读取该 JSON 并用Process.Start(new ProcessStartInfo { FileName = newExe, Arguments = savedArgs, WorkingDirectory = savedDir })显式传入 - 若原程序是以管理员权限运行的,
Updater.exe必须同样请求requireAdministrator清单,否则重启后权限降级,某些功能直接失效 - 别依赖
Application.Restart()—— 它只是重新启动当前进程镜像,不加载新文件,也不处理路径/参数传递
最易被忽略的点是:旧进程退出与新文件复制之间的时间差。哪怕只差几毫秒,File.Copy 仍可能因句柄未完全释放而失败。加延时不是权宜之计,而是 Windows 文件系统机制决定的刚性要求。











