记录一次WPF程序进程挂起问题
1. 使用背景
开发WPF
单进程项目,在项目中使用MongoDB
数据库,需要连接多个不同的数据库实例,另外项目框架采用了事件聚合器来管理模块间的通知调用,基于NetMQ
实现了一个ZeroMQPublisher
和ZeroMQSubscriber
。
事件聚合器服务实现方案:
ZeroMQPublisher
启动时会监听本机地址的一个端口(比如tcp://*:5866
)ZeroMQSubscriber
启动时需要去连接NetMQ服务端(比如:tcp://127.0.0.1:5866
)
启动数据库实例的实现方案:
在C#
代码中通过Process
来启动mongod.exe
,启动时分别指定不同的启动参数(--dbpath
--port
--replSet
--logpath
等等)。该逻辑在应用程序启动时调用。
- 具体实现代码片段如下:
var startInfo = new ProcessStartInfo
{
FileName = processName,
Arguments = $"--dbpath \"{mongoConfig.DBPath}\" --port {mongoConfig.Port} --replSet {mongoConfig.ReplicaSetName} " +
$"--logpath {Path.Combine(mongoConfig.DBPath, "mongod.log")} --logappend",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
// 指定用于读取标准输出流的编码。指定为 UTF8 可以确保正确读取输出
StandardOutputEncoding = Encoding.UTF8,
// 指定用于读取标准错误流的编码。同样,指定为 UTF8 可以确保正确读取错误输出
StandardErrorEncoding = Encoding.UTF8
};
var _mongoProcess = new Process { StartInfo = startInfo };
_mongoProcess.Start();
// 订阅输出和错误事件
_mongoProcess.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
outputMsgCallback?.Invoke("MongoDB 输出: " + e.Data);
}
};
_mongoProcess.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
outputMsgCallback?.Invoke("MongoDB 错误: " + e.Data);
}
};
_mongoProcess.BeginOutputReadLine();
_mongoProcess.BeginErrorReadLine();
注:为了实现数据库实例在WPF
程序退出后依然可以连接,故在退出程序时,未清除上面的Process
资源。
- 实现效果:
- a. 可以通过配置启动多个不同的
mongo db
数据库实例; - b. 可以将
mongo db
数据库实例启动的日志重定向输出,并通过outputMsgCallback
的定义来记录起来(已有指定的 --logpath ,其实可以不需要重定向的输出信息)
- a. 可以通过配置启动多个不同的
2. 问题现象
WPF
程序退出后,再次启动程序,会提示如下报错信息:
内部异常:
SocketException: 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
- 检查端口占用情况,执行
netstat -ano | finstr :5866
,返回如下信息:
TCP 0.0.0.0:5866 0.0.0.0:0 LISTENING 46064
TCP 127.0.0.1:3172 127.0.0.1:5866 ESTABLISHED 46064
TCP 127.0.0.1:5866 127.0.0.1:3172 ESTABLISHED 46064
通过上面显示的进程id 46064
去查询对应的进程(tasklist /FI "PID eq 46064"
),显示查询不到对应的进程信息。
3. 问题原因及分析
- 检查实现逻辑,发现在应用程序退出时,已调用对应的
NetMQ
的Dispose
释放逻辑; - 优化尝试:在释放连接资源前,先执行
DiscConnect
逻辑亦无法解决问题; - 工具分析
[TCPView](https://learn.microsoft.com/en-us/sysinternals/downloads/tcpview?spm=5aebb161.543df828.0.0.737f7038lsFU2W)
使用TCPView
工具可以看到上面对应的3个tcp
的连接信息,但是无法查看到关联的实际进程信息,依然无法解决问题。
4. 使用[ProcessExplorer](https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer)
工具查看进程信息,选择mongod.exe
,查看属性,发现其会显示Parent
为上面的WPF应用进程。
kill
这两个mongod.exe
后,不再出现上面的端口占用问题。因此,问题的主要原因是mongod.exe
启动的数据库实例进程和主进程是关联在一起的,根本原因是Process
启动进程是未和主进程相隔离。
4. 解决方案
- 修改进程启动方式:
var startInfo = new ProcessStartInfo
{
FileName = processName,
Arguments = $"--dbpath \"{mongoConfig.DBPath}\" --port {mongoConfig.Port} --replSet {mongoConfig.ReplicaSetName} " +
$"--logpath {Path.Combine(mongoConfig.DBPath, "mongod.log")} --logappend",
UseShellExecute = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
- 通过
Shell
来启动进程(UseShellExecute = true
),而不是直接由当前应用程序启动; - 隐藏启动的
Shell
窗口(WindowStyle = ProcessWindowStyle.Hidden
);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能