)
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:ClassWpfApp_t1.App xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:localclr-namespace:WpfApp_t1 StartupApplication_Startup ExitApplication_Exit Application.Resources /Application.Resources /Application !-- Application x:ClassWpfApp_t1.App 定义WPF应用程序的入口类指定代码隐藏文件为WpfApp_t1命名空间下的App类 xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation 引入WPF核心呈现引擎的XML命名空间提供所有WPF控件 xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml 引入XAML语言本身的命名空间提供x:前缀的指令如x:Class、x:Name等 xmlns:localclr-namespace:WpfApp_t1 引入本地项目的WpfApp_t1命名空间允许使用项目中的自定义类型 StartupApplication_Startup 指定应用程序启动时触发的事件处理方法 ExitApplication_Exit 指定应用程序退出时触发的事件处理方法 Application.Resources 定义应用程序级别的资源字典存放可全局共享的资源如样式、模板、字符串等 /Application.Resources /Application --在默认的WPF程序中这个代码StartupUriMainWindow.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 namee启动事件参数包含命令行参数等信息/param protected override void OnStartup(StartupEventArgs e) { // 声明布尔变量isNewInstance用于标识是否为新实例 bool isNewInstance; // 创建一个新的互斥体对象 // 参数1initiallyOwned设置为true表示调用线程拥有互斥体的初始所有权 // 参数2name指定互斥体的唯一名称 // 参数3out 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)) { // 判断当前遍历到的进程是否为当前进程本身 // 通过比较进程IDProcessId来区分 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 namee退出事件参数/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 namesender事件发送者Application对象/param /// param namee启动事件参数/param private void Application_Startup(object sender, StartupEventArgs e) { // 预留的启动事件处理逻辑当前未实现具体功能 } /// summary /// Application_Exit事件处理方法 /// 该方法与XAML中的Exit事件绑定当前为空实现 /// /summary /// param namesender事件发送者Application对象/param /// param namee退出事件参数/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.说明主窗体类 MainWindow按自己的需求编程即可。不影响单例运行实现。Application_Startup函数和Application_Exit函数可按需求实现。如不需要可删除。ActivateExistingInstance函数用于实现已有实例在运行的情况下激活已存在的窗口如不需要可删除。三、核心功能总结单例保证通过 Mutex / 进程检测确保只有一个实例运行自动激活重复打开时会自动唤醒已最小化 / 后台的窗口并置顶显示无残留程序退出时自动释放互斥体不会导致无法再次启动特点基于Mutex稳定无坑支持窗口激活