当前位置: 首页 > news >正文

C# WPF 单例运行实现(实现1)

C# WPF 单例运行实现(实现1)

摘要

本文讲述C# WPF应用程序的单例运行的必要性和程序实现(实现1)。使用一个静态的互斥体对象,实现单实例运行机制,用于防止多个进程同时执行某段代码。能够有效防止老旧慢、小内存的工控机重复启动同一应用程序引起的程序崩溃。

一、前言

应用程序的单例运行通俗的讲就是同样应用程序在同一主机上的某一时刻只能运行一个,即同样应用程序在同一主机上的某一时刻只能有一个实例在运行。

在上位机的应用中经常会出现这样的情况,由于计算机比较旧(性能差)或内存比较小,单击启动一个WPF应用程序后,屏幕无反应。这时候大多数用户会认为没有点击到或者程序没启动,最简单的操作就是再单击一次,这时候其实已经启动了第二个同样WPF应用程序。

如果WPF应用程序涉及到了设备操作、通信或一些独占性资源操作的时候,程序必然会出错。例如我们在WPF应用程序涉及到了串口操作,而串口它是一个独占的资源,两次打开同一个串口必然会引起程序的出错,如果例外程序没有处理这种错误甚至会引起程序崩溃。

这样的情况带有普遍性。因为默认情况下,C#程序(包括WPF程序)会被编译成IL 代码(中间语言)以程序集的形式存放于磁盘上,当我们点击启动这个应用程序的时候,这个IL 代码会在CLR(公共语言运行时)的控制下进行加载,并使用JIT 动态编译编译成当前目标机机器码,然后再运行。这个过程(在CLR管理下编译,加载,运行)相对是比较费时间的,加上计算机比较旧或内存比较小,所以就造成了这种情况普遍性存在。

二、程序实现程序及核心原理

1.程序实现程序及核心原理

使用WPF应用程序模板,.NET 10.0 目标框架建一工程。对App.xaml和App.xaml.cs做如下的修改。

App.xaml:

<Application x:Class="WpfApp_t1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp_t1" Startup="Application_Startup" Exit="Application_Exit"> <Application.Resources> </Application.Resources> </Application> <!-- <Application x:Class="WpfApp_t1.App" 定义WPF应用程序的入口类,指定代码隐藏文件为WpfApp_t1命名空间下的App类 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 引入WPF核心呈现引擎的XML命名空间,提供所有WPF控件 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 引入XAML语言本身的命名空间,提供x:前缀的指令如x:Class、x:Name等 xmlns:local="clr-namespace:WpfApp_t1" 引入本地项目的WpfApp_t1命名空间,允许使用项目中的自定义类型 Startup="Application_Startup" 指定应用程序启动时触发的事件处理方法 Exit="Application_Exit" 指定应用程序退出时触发的事件处理方法 > <Application.Resources> 定义应用程序级别的资源字典,存放可全局共享的资源(如样式、模板、字符串等) </Application.Resources> </Application> -->

在默认的WPF程序中,这个代码:StartupUri="MainWindow.xaml"用来启动主窗口,现在改用重写Application基类的OnStartup方法启动主窗口。

这是为了在启动窗口前,用来检测有无本应用的实例已在运行。如果本应用的实例已经在运行,则不再启动本应用的新实例,进行相应的处理:激活已存在的窗口,关闭新创建的实例;如果没有本应用的实例在运行,则创建初始化新实例,从主窗口开始启动运行。

App.xaml.cs:

// 引入系统配置相关的命名空间,用于读取应用程序配置信息 using System.Configuration; // 引入数据相关的命名空间,提供数据访问和处理能力 using System.Data; // 引入运行时互操作服务命名空间,用于调用Windows API函数 using System.Runtime.InteropServices; // 引入WPF应用程序基础命名空间,提供WPF框架核心功能 using System.Windows; // 定义应用程序的命名空间,组织相关类和资源 namespace WpfApp_t1 { /// <summary> /// App.xaml的交互逻辑类 /// 继承自Application基类,是WPF应用程序的入口点 /// </summary> public partial class App : Application { // 声明一个静态的互斥体对象,用于实现单实例运行机制 // 互斥体(Mutex)是一种同步原语,用于防止多个进程同时执行某段代码 private static Mutex _mutex; // 定义互斥体的唯一名称,使用GUID确保全局唯一性 // 格式为"程序名-GUID",避免与其他程序冲突 private const string UniqueMutexName = "MyWpfApp-550E8400-E29B-41D4-A716-446655440000"; // 使用DllImport特性导入user32.dll中的SetForegroundWindow函数 // 该函数用于将指定窗口设置为前台窗口(激活并置顶显示) [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); // 使用DllImport特性导入user32.dll中的ShowWindow函数 // 该函数用于设置窗口的显示状态(如最大化、最小化、还原等) [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); // 定义窗口显示状态常量SW_RESTORE,值为9 // 表示将最小化的窗口还原到原来的大小和位置 private const int SW_RESTORE = 9; /// <summary> /// 重写Application基类的OnStartup方法 /// 该方法在应用程序启动时被调用,是应用程序初始化的入口 /// </summary> /// <param name="e">启动事件参数,包含命令行参数等信息</param> protected override void OnStartup(StartupEventArgs e) { // 声明布尔变量isNewInstance,用于标识是否为新实例 bool isNewInstance; // 创建一个新的互斥体对象 // 参数1:initiallyOwned设置为true,表示调用线程拥有互斥体的初始所有权 // 参数2:name指定互斥体的唯一名称 // 参数3:out isNewInstance返回值,true表示创建了新互斥体(无其他实例),false表示互斥体已存在(已有实例) _mutex = new Mutex(true, UniqueMutexName, out isNewInstance); // 判断是否为新实例 if (isNewInstance) { // 如果是新实例,调用基类的OnStartup方法完成初始化 base.OnStartup(e); // 创建主窗口实例 MainWindow mainWindow = new MainWindow(); // 显示主窗口 mainWindow.Show(); } else { // 如果不是新实例(已有实例在运行),激活已存在的窗口 ActivateExistingInstance(); // 关闭当前新启动的实例 Shutdown(); } } /// <summary> /// 激活已存在的程序窗口的私有方法 /// 通过遍历进程列表找到同名进程并激活其主窗口 /// </summary> private void ActivateExistingInstance() { // 获取当前进程对象,包含进程ID、进程名等信息 var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); // 获取所有与当前进程同名的进程列表 foreach (var process in System.Diagnostics.Process.GetProcessesByName(currentProcess.ProcessName)) { // 判断当前遍历到的进程是否为当前进程本身 // 通过比较进程ID(ProcessId)来区分 if (process.Id != currentProcess.Id) { // 调用ShowWindow函数,将找到的进程主窗口还原显示 // 参数1:进程的主窗口句柄(MainWindowHandle) // 参数2:显示状态常量SW_RESTORE(还原窗口) ShowWindow(process.MainWindowHandle, SW_RESTORE); // 调用SetForegroundWindow函数,将窗口设置为前台窗口 // 使窗口获得焦点并显示在最顶层 SetForegroundWindow(process.MainWindowHandle); // 找到并激活已有实例后,跳出循环 break; } } } /// <summary> /// 重写Application基类的OnExit方法 /// 该方法在应用程序退出时被调用,用于清理资源 /// </summary> /// <param name="e">退出事件参数</param> protected override void OnExit(ExitEventArgs e) { // 使用空合并运算符(?.)确保_mutex不为null时才调用ReleaseMutex方法 // 释放互斥体的所有权,允许其他进程获取该互斥体 _mutex?.ReleaseMutex(); // 使用空合并运算符(?.)确保_mutex不为null时才调用Close方法 // 关闭互斥体对象,释放相关资源 _mutex?.Close(); // 调用基类的OnExit方法完成退出处理 base.OnExit(e); } /// <summary> /// Application_Startup事件处理方法 /// 该方法与XAML中的Startup事件绑定,当前为空实现 /// </summary> /// <param name="sender">事件发送者(Application对象)</param> /// <param name="e">启动事件参数</param> private void Application_Startup(object sender, StartupEventArgs e) { // 预留的启动事件处理逻辑,当前未实现具体功能 } /// <summary> /// Application_Exit事件处理方法 /// 该方法与XAML中的Exit事件绑定,当前为空实现 /// </summary> /// <param name="sender">事件发送者(Application对象)</param> /// <param name="e">退出事件参数</param> private void Application_Exit(object sender, ExitEventArgs e) { // 预留的退出事件处理逻辑,当前未实现具体功能 } } }

在全局性的App类中申明静态Mutex的对象 _mutex,用以实现单实例运行机制,用于防止多个进程同时执行App类的初始化、主窗体的实例化和运行显示。

Mutex类用来实现跨进程互斥锁,归属线程。可以命名,命名 Mutex 能被其他进程找到拥有线程。作用:保证同一时间只有一个线程 / 进程执行某段代码、访问某个资源;特点:跨进程可用(系统级内核对象),这是它和lock最大的区别。

UniqueMutexName的定义采用使用GUID工具产生,确保全局唯一性。进而保证不会有重名的线程,保证对象 _mutex的归属线程的唯一性。

语句:_mutex = new Mutex(true, UniqueMutexName, out isNewInstance) 在实例化对象Mutex的同时,检测是否已有同样UniqueMutexName名称的实例在运行。这是关键语句。

有同名称的实例在运行说明同样的实例已在运行。做后续处理。

2.说明

  1. 主窗体类 MainWindow按自己的需求编程即可。不影响单例运行实现。

  2. Application_Startup函数和Application_Exit函数,可按需求实现。如不需要可删除。

  3. ActivateExistingInstance函数,用于实现已有实例在运行的情况下,激活已存在的窗口,如不需要可删除。

三、核心功能总结

  1. 单例保证:通过 Mutex / 进程检测,确保只有一个实例运行
  2. 自动激活:重复打开时,会自动唤醒已最小化 / 后台的窗口,并置顶显示
  3. 无残留:程序退出时自动释放互斥体,不会导致无法再次启动
  4. 特点:基于Mutex,稳定无坑,支持窗口激活
http://www.jsqmd.com/news/848490/

相关文章:

  • Perplexity薪资数据获取全链路指南(从认证绕过到JSON解析实操)
  • 重庆数据备份公司哪个好
  • 2026智慧公厕推荐榜:杭州智慧公厕系统/上海智慧公厕卫生间改造/上海智慧公厕系统/上海智慧厕所/杭州智慧公厕卫生间改造/选择指南 - 优质品牌商家
  • 非近轴衍射分束器的设计与严格分析
  • LinuxXFS元数据异常定位实战
  • AI数字人驱动的矩阵内容生产:2026年技术架构与人效革命
  • 2026年工地集装箱房厂家TOP5排行:工地钢结构棚/彩钢储煤棚/拌合站彩钢雨棚/搅拌站料仓/搅拌站防护棚/砂石料棚/选择指南 - 优质品牌商家
  • CVPR投稿后,我是如何用3天时间写好Rebuttal并成功说服审稿人的?
  • 2026出国劳务选靠谱公司:出国务工正规劳务公司、出国劳务出国务工、出国劳务哪里工资高、劳务输出公司出国务工、劳务输出出国务工选择指南 - 优质品牌商家
  • YOLOv11仓库托盘与孔洞目标检测数据集-410张-pallet-1_7
  • 初创团队如何利用 Taotoken 的 Token Plan 有效控制 AI 开发成本
  • 青岛石韵坊:2026年5月市场新观察,解析高端电视背景墙定制新标杆 - 2026年企业推荐榜
  • 2026年new趋势下,如何选择成都专业的激光空压机服务商? - 2026年企业推荐榜
  • LPC900系列ICP编程模式详解与Keil工具链配置
  • RabbitMQ 如何开启 SSL 加密连接配置步骤
  • 2026耐用汽车北斗定位器:无线定位器/汽车定位器/物流车北斗定位器/电动车定位器/货物定位器/车载定位器/车辆北斗定位器/选择指南 - 优质品牌商家
  • 观察使用Token Plan套餐前后月度AI调用成本的变化趋势
  • 如何实现10倍速GitHub下载:智能加速插件完整配置指南
  • RAG 不仅仅是向量库对接:深入解析其三大复杂挑战与工程实践
  • 2026年严选:比较好的全屋定制企业 - 品牌推广大师
  • SpringBoot项目实战:集成iText7 HTML转PDF,并处理中文、文件流与OSS上传
  • 2026年Q2优质玻璃纤维制造厂名录:玻璃纤维厂家/玻璃纤维品牌/玻璃纤维工厂/玻璃纤维源头厂家/玻璃纤维生产商/选择指南 - 优质品牌商家
  • YOLOv11城市道路车辆与行人目标检测数据集-7015张-Aerial-Person-Detection-1
  • Windows 11终极优化指南:使用Win11Debloat一键清理系统冗余提升性能
  • 别再死记硬背了!用CubeMX和Keil5,5分钟搞懂STM32F103C8T6的内存映射与位带操作
  • 2026热门商用热水开水器盘点:电热水器烧开水机、连锁餐饮开水机、餐厨用桶装水设备、餐厨用纯水设备、餐饮用纯水机选择指南 - 优质品牌商家
  • FPGA新手避坑指南:用Verilog手搓一个SPI Flash控制器(以W25Q64为例)
  • 机器学习篇---四阶特征矩
  • 2026塑料管道采购指南:公元好房子、公元家装管、公元工矿、公元工程服务、公元工装管、公元市政、公元开关、公元排水选择指南 - 优质品牌商家
  • ARM ETE架构:嵌入式系统调试与性能分析利器