最可靠方案是Windows服务配sc恢复策略或独立父进程监听子进程退出;需避免共用资源、加超时等待、分离UI逻辑,并注意native崩溃等不可控场景。

程序崩溃后怎么让 C# 进程自己拉起来
Windows 下没有现成的“守护进程”概念,C# 本身也不提供进程保活机制。想实现崩溃重启,得靠外部手段——最可靠的是用 Windows 服务 + sc 配置自动重启策略,或者用父进程监听子进程退出。直接在同一个 Process 里 try/catch 是无效的,未处理异常会让进程彻底终止,catch 不到。
- 别在主线程里写
while(true) { try { Main(); } catch {} }—— 这拦不住StackOverflowException、OutOfMemoryException或 P/Invoke 崩溃 - 如果只是桌面程序,推荐用一个轻量父进程(比如一个空壳
ConsoleApp),用Process.Start()启动主程序,再调用process.WaitForExit()+Process.Start()循环拉起 - 父进程必须独立部署、不和子进程共用配置或目录,否则子进程崩溃可能连带影响父进程(比如共享了锁文件、损坏了 config.json)
用 Windows 服务实现稳定重启的关键配置
服务方式比父进程更稳,但默认不会自动重启崩溃的可执行文件——它只管服务宿主进程(svchost.exe)。真正起作用的是服务恢复选项,得用 sc 命令手动设,.NET 的 ServiceBase 本身不控制这个。
- 服务安装后,必须运行:
sc failure "YourServiceName" reset= 0 actions= restart/60000/restart/60000/restart/60000 -
reset= 0表示不重置失败计数器(否则三次失败后就停了);restart/60000指第一次失败后 60 秒重启,单位是毫秒 - 服务程序的
OnStart里不要阻塞,要立刻启动一个新线程或Task执行主逻辑,否则服务管理器会判定启动超时(默认 30 秒) - 如果主逻辑是 WPF 或 WinForms,不能直接在服务里显示 UI——会卡在 Session 0,需改用无界面模式或分离为单独进程
父进程方案中容易被忽略的退出信号问题
用 Process 启子进程时,WaitForExit() 能捕获正常退出,但对强制杀进程(比如任务管理器结束、taskkill /f)也有效;不过子进程若因 Windows 错误报告(WER)弹窗卡住(比如“程序已停止工作”),它其实没真正退出,父进程会一直等下去。
- 务必给
WaitForExit()加超时,比如process.WaitForExit(30000),超时后用process.HasExited再判断一次 - 子进程启动时建议加参数控制行为:
process.StartInfo.Arguments = "--no-ui --auto-restart",方便内部识别是被拉起的 - 避免父子进程写同一个日志文件——可能因文件锁导致子进程启动失败,建议用时间戳或 PID 做日志名,如
log_$(PID).txt
C# 自己调用 Environment.Exit() 算不算崩溃
不算。这是受控退出,Process.Exited 事件能正常触发,父进程可以立刻拉起新实例。但要注意:如果子进程在退出前释放了独占资源(比如删了自己所在的目录、卸载了驱动),新进程可能起不来。
-
Environment.Exit(0)和return效果一样,不会触发 Windows 的错误报告(WER) - 但
Environment.FailFast("msg")会绕过所有 finally 和终结器,相当于模拟崩溃,此时父进程能捕获退出,但无法做清理 - 如果主程序用了
AppDomain.UnhandledException或TaskScheduler.UnobservedTaskException做兜底,记得在 handler 里别调Environment.Exit()——它会让整个进程立即消失,父进程来不及反应
真正难搞的是 GPU 驱动崩溃、AV 软件拦截、Windows 更新强制重启这类场景,任何用户态的守护逻辑都无能为力。这时候得看日志里是不是反复出现 0xE06D7363 或 0xC0000005,优先排查 native 依赖和内存访问问题。










