记录一次WPF程序进程挂起问题

1. 使用背景

开发WPF单进程项目,在项目中使用MongoDB数据库,需要连接多个不同的数据库实例,另外项目框架采用了事件聚合器来管理模块间的通知调用,基于NetMQ实现了一个ZeroMQPublisherZeroMQSubscriber

事件聚合器服务实现方案

  • 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 ,其实可以不需要重定向的输出信息)

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. 问题原因及分析

  1. 检查实现逻辑,发现在应用程序退出时,已调用对应的NetMQDispose释放逻辑;
  2. 优化尝试:在释放连接资源前,先执行DiscConnect逻辑亦无法解决问题;
  3. 工具分析 [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. 解决方案

  1. 修改进程启动方式:

    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);
相关推荐
创码小奇客4 小时前
MongoDB 聚合操作,有手就行?
mysql·mongodb·trae
猫霸1 天前
WPF静态资源StaticResource和动态资源DynamicResource有什么区别,x:Static又是什么意思?
分布式·c#·.net·wpf
wqq10271 天前
WPF 从Main()方法启动
wpf
明耀1 天前
WPF ListBox双击事件
wpf
wqq10271 天前
WPF 依赖注入启动的问题
wpf
wqq10271 天前
WPF 使用 DI EF CORE SQLITE
sqlite·wpf
手揽回忆怎么睡2 天前
mongodb学习
数据库·学习·mongodb
pursue.dreams2 天前
Windows 下 MongoDB ZIP 版本安装指南
数据库·windows·mongodb
是大强2 天前
mongodb 远程访问
数据库·mongodb
双叶8362 天前
(51单片机)LCD显示日期时间时钟(DS1302时钟模块教学)(LCD1602教程)
c语言·开发语言·数据库·单片机·嵌入式硬件·mongodb·51单片机