## 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. Taro微信小程序获取Tab页可视区域高度

    前情 公司有自己的小程序项目,因公司主要技术栈为react,所以选择了Taro来开发,Taro是京东出品的多端统一开发解决方案,用来开发小程序也相比用原生开发,在开发体验上好很多,而且还能使用成熟的R ...

  2. 树莓派设置CPU运行的核心数为3,保留核心4号

    具体步骤:1.打开终端,输入 sudo nano /boot/cmdline.txt2.在第一行最后空一格加上isolcpus=33.保存

  3. 11C++循环结构-for循环(1)——教学

    一.for语句 (第27课 老狼老狼几点钟)参考1 引出问题: 当需要重复执行某一语句时,使用for语句.for语句最常用的格式为: for (循环变量赋初值:循环条件:循环变量增值) 语句: 注: ...

  4. Tauri2.0-Vue3OS桌面端os平台|tauri2+vite6+arco电脑版OS管理系统

    自研tauri2.x+vite6+vue3+arco.design客户端os管理系统Tauri2ViteOS. vue3-tauri2-os原创跨平台Tauri2.0+Vite6+Pinia2+Arc ...

  5. Java调用与发布Webservice接口(一)

    ​ 一  准备工作 (一)开发环境 demo以springboot为基础框架,使用到了httpclient.hutool等依赖,详情如下:         springboot版本: org.spri ...

  6. SpringBoot结合Liquibase实现数据库变更管理

    https://juejin.cn/post/7171232605478584328 https://juejin.cn/post/7170857098538909732 前言 研发过程中经常涉及到数 ...

  7. 龙哥收集的Github资源——Python量化不要自己造轮子

      打开github后,在搜索框中输入下面的项目作者及项目名称,然后点一下 All Github 搜索 格式 user:xxxxx in:name xxxxxxxxxxxxx 举例 user:Cken ...

  8. Qt音视频开发18-海康sdk回调

    一.前言 海康sdk显示实时视频流除了支持句柄方式以外,也支持回调的方式拿到每一张图片自己绘制处理,当然回调除了拿到视频数据,其实音频数据也一块拿到了,自行调用音频设备播放就行,关于海康sdk回调这块 ...

  9. STK V11.2 64位下载及视频

    1.AGI Systems Tool Kit (STK) 11.2 64位+含补丁激活教程:下载地址 2.STK视频学习资料(一):下载地址

  10. 百度高效研发实战训练营-Step3

    .# 百度高效研发实战训练营-Step3 3.1 质量意识 关于本部分,将从以下几点进行介绍: 3.1.1 质量的基本概念 意识的树立,源于认识的提高. 首先介绍质量的基本概念,主要包括以下内容: 1 ...