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

## 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" `),显示查询不到对应的进程信息。
![image.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/7b2961ea2e8a4d3981a3a626ae0c4ee5~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgd3I=:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTcwMjUxNDQyMTY2MjE3NCJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1740497408&x-orig-sign=DB6FDDvNXEfPT%2BSFB%2BOhKyAH%2FZI%3D)

## 3. 问题原因及分析

1. 检查实现逻辑,发现在应用程序退出时,已调用对应的`NetMQ`的`Dispose`释放逻辑;
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应用进程。

![image.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/8680b2b77ffa42f7867243dca86a85c9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgd3I=:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTcwMjUxNDQyMTY2MjE3NCJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1740497408&x-orig-sign=hF3tIlAnjA%2F0heHM%2FTEsSyuhwzM%3D)

![image.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/05d200bceb28409886af06ee3ae14465~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgd3I=:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTcwMjUxNDQyMTY2MjE3NCJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1740497408&x-orig-sign=mb74sIykM09OU7WY1HtshHplCv0%3D)

`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`);

记录一次WPF程序进程挂起问题的更多相关文章

  1. c# 守护进程,WPF程序自守护

    原文:c# 守护进程,WPF程序自守护 版权声明:本文为博主原创文章,转载请注明出处. https://blog.csdn.net/lwwl12/article/details/79035246 如何 ...

  2. WPF 程序如何跨窗口/跨进程设置控件焦点

    原文:WPF 程序如何跨窗口/跨进程设置控件焦点 WPF 程序提供了 Focus 方法和 TraversalRequest 来在 WPF 焦点范围内转移焦点.但如果 WPF 窗口中嵌入了其他框架的 U ...

  3. WPF 程序如何移动焦点到其他控件

    原文:WPF 程序如何移动焦点到其他控件 WPF 中可以使用 UIElement.Focus() 将焦点设置到某个特定的控件,也可以使用 TraversalRequest 仅仅移动焦点.本文介绍如何在 ...

  4. Linux C 程序 进程控制(17)

    进程控制 1.进程概述现代操作系统的特点在于程序的并行执行.Linux是一个多用户多任务的操作系统.ps .pstree 查看进程进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得.// ...

  5. WPF 程序中启动和关闭外部.exe程序

    当需要在WPF程序启动时,启动另一外部程序(.exe程序)时,可以按照下面的例子来: C#后台代码如下: using System; using System.Collections.Generic; ...

  6. [Python 学习]2.5版yield之学习心得 - limodou的学习记录 - limodou是一个程序员,他关心的焦点是Python, DocBook, Open Source …

    [Python 学习]2.5版yield之学习心得 - limodou的学习记录 - limodou是一个程序员,他关心的焦点是Python, DocBook, Open Source - [Pyth ...

  7. Winform 程序嵌入WPF程序 并发送消息

    废话不多说,先看解决方案目录 WindowsFormsDemo是主程序,WpfApp是嵌入的WPF程序,先看WPF程序,程序默认启动的页面是MainWindow.xaml,这里注释掉App.xaml里 ...

  8. WPF 一个空的 WPF 程序有多少个窗口

    原文:WPF 一个空的 WPF 程序有多少个窗口 好多小伙伴说 WPF 的程序有五个窗口,但是我尝试使用了 EnumThreadWindows 去获取的时候居然拿到了 10 多个窗口 在 WPF 内部 ...

  9. 进程 PCB 进程挂起

    7-1 进程定义  OS系统从只能跑一个程序到能跑多个.进程可以描述程序的执行过程. 进程:一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程. 只有当一个程序被OS加载到内存中,cpu对其 ...

  10. 在WPF程序中打开网页:使用代理服务器并可进行JS交互

    本项目环境:使用VS2010(C#)编写的WPF程序,通过CefSharp在程序的窗体中打开网页.需要能够实现网页后台JS代码中调用的方法,从网页接收数据,并能返回数据给网页.运行程序的电脑不允许上网 ...

随机推荐

  1. 【实战问题】-- 布隆过滤器的三种实践:手写,Redission以及Guava(2)

    前面我们已经讲过布隆过滤器的原理[实战问题]-- 缓存穿透之布隆过滤器(1),都理解是这么运行的,那么一般我们使用布隆过滤器,是怎么去使用呢?如果自己去实现,又是怎么实现呢? 目录 布隆过滤器 手写布 ...

  2. uni-app Vue3项目引入Tailwind CSS

    前情 Tailwind CSS 是一个原子类 CSS 框架,它将基础的 CSS 全部拆分为原子级别,同时还补全各种浏览器模式前缀,兼容性也不错.它的工作原理是扫描所有 HTML 文件.JavaScri ...

  3. 【原创】PREEMPT-RT中断线程化原理与中断线程优先级设置

    PREEMPT-RT中断线程化与中断线程优先级设置 目录 PREEMPT-RT中断线程化与中断线程优先级设置 一.什么是中断线程化 1. 普通Linux中断处理 2. 实时性的不足 3. 中断线程化 ...

  4. django推导流程

    目录 一.纯手撸web框架 二.基于wsgiref模块 三.代码封装优化 四.动静态网页 五.jinja2模块 六.前端.后端.数据库三者联动 一.纯手撸web框架 1.web框架的本质 理解1:连接 ...

  5. 【Python】HTML中Base64存储的图片转为本地图片文件

    我用jupyter notebook写了笔记之后,想导出markdown,然后导出不了,我就只能导出html,结果导出的html存储图片用的base64的方式-- 于是我就要把导出的html文档里面的 ...

  6. Qt编写手机端视频播放器/推流工具/Onvif工具

    一.视频播放器 同时支持多种解码内核,包括qmedia内核(Qt4/Qt5/Qt6).ffmpeg内核(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6).vlc内核(v ...

  7. Qt开发经验小技巧256-260

    默认QDialog窗体右下角有个拉伸尺寸的手柄,通过它可以对窗体拉伸大小,这个控件很容易被遗忘但是又经常可以看到,他的名字叫QSizeGrip,可以通过setSizeGripEnabled来启用或者禁 ...

  8. Qt编写安防视频监控系统65-子模块9数据调试

    一.前言 数据调试模块,用于显示通信串口的数据,自定义不同颜色显示,可以勾选过滤某个串口进行数据查看,也可以选择所有数据,还可以勾选暂停显示复选框用来暂停打印显示信息.数据调试可以很方便的查看串口收发 ...

  9. Qt音视频开发42-人脸识别客户端

    一.前言 人脸识别客户端程序,不需要和人脸识别相关的库在一起,而是通过协议通信来和人脸识别服务端通信交互,人脸识别客户端和服务端程序框架,主要是为了提供一套通用的框架,按照定好的协议,实现人脸识别的相 ...

  10. 在C#中通过使用Newtonsoft.Json库来解析天地图地理编码(GeoCoder)服务接口返回的Json格式的数据,以及HttpWebRequest 设置不完全时服务器返回“远程服务器返回错误: (403) 已禁止”解决方法

    天地图地理编码(GeoCoder)服务接口返回的Json格式的数据,如下所示: http://api.tianditu.gov.cn/geocoder?ds={"keyWord": ...