记录一次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 小时前
界面组件DevExpress WPF中文教程:Grid - 如何过滤节点?
.net·wpf·界面控件·devexpress·ui开发
I'mSQL4 小时前
C#与WPF使用mvvm简单案例点击按钮触发弹窗
开发语言·c#·wpf
Wang's Blog8 小时前
Nestjs框架: 基于Mongodb的多租户功能集成和优化
数据库·mongodb·多租户
鼠鼠我捏,要死了捏8 小时前
深入解析MongoDB分片原理与运维实践指南
mongodb·性能优化·sharding
~央千澈~1 天前
MongoDB数据库详解-针对大型分布式项目采用的原因以及基础原理和发展-卓伊凡|贝贝|莉莉
数据库·mongodb
百锦再1 天前
WPF依赖属性深度解析:从原理到高级应用
wpf·依赖·绑定·验证·net·强制
✎ ﹏梦醒͜ღ҉繁华落℘1 天前
WPF高级学习(一)
学习·wpf
界面开发小八哥1 天前
界面控件DevExpress WPF v25.1新版亮点:模板库更新升级
ui·.net·wpf·界面控件·devexpress·ui开发
△曉風殘月〆1 天前
WPF MVVM进阶系列教程(二、数据验证)
wpf·mvvm
JosieBook2 天前
【开源】WpfMap:一个基于WPF(Windows Presentation Foundation)技术构建的数据可视化大屏展示页面
信息可视化·wpf