1. C# 进程间同步机制(Mutex和EventWaitHandle)实现单一应用启动

文章目录

      • 一. 技能目标
      • 二. 技能知识点介绍
        • ① Mutex(互斥量)
        • ② EventWaitHandle(事件等待句柄)
      • 三. 在WPF应用程序中启动程序的时候检查应用是否已经启动,如果已经启动就将主窗口显示出来

一. 技能目标

在开发应用程序的过程中,我们会遇到这样的情况,当我们启动一个应用的时候,如果这个应用已经启动了,我们就展示已经启动的应用就可以,如果没有启动,就正常启动这个应用.怎么实现这个功能呢? 答案是使用进程间通信,使用语言来表述就是在启动新进程的时候去检测某个进程间之间共享的一个变量,然后如果发现这个变量已经存在,就通知那个已经启动了的进程显示主窗口,然后关闭当前正在启动的新进程(新应用).

二. 技能知识点介绍

① Mutex(互斥量)

什么是互斥量? 互斥量用于确保应用程序只有一个实例在运行,思路就是在应用程序的开始启动部分,去创建一个互斥量,创建互斥量的方式如下,互斥量的创建,需要一个常量字符串MutexId,来唯一标识这个信号量

private const string _mutexId = "MutexTest-2024-04-08-Fioman";
private static Mutex? _mutex;
_mutex = new Mutex(true, _mutextId, out bool CreatedNew);
  • 参数解释

  • true

调用线程是否拥有新创建的互斥量的所有权,true表示拥有该互斥量的所有权.拥有Mutex互斥量的所有权是什么意思呢?
拥有Mutex互斥量的所有权意味着该线程可以决定何时释放互斥量.,而其他的线程必须等待所有权释放后才能获取它.说的直白点就是,设置为true就是立马要获取这个资源,如果这个资源已经被占用了,就获取不到了.如果设置为false,延迟同步操作,这个时候创建_mutex的时候是没有要求获取所有权的,就是还不要求去操作这个资源,但是什么时候请求操作这个资源呢,需要手动的调用WaitOne() 来请求所有权.

① true的使用场景

单实例应用程序,确保一个应用程序在运行,这个时候你需要立即检查并阻止多个实例的运行.


namespace TestSimple
{
    public class MutexSimple
    {
        private const string mutexId = "MutexSimpleId";

        public static void SingleInstanceCheck()
        {
            using (Mutex mutex = new Mutex(true, mutexId, out bool IsCreateNew))
            {
                if (!IsCreateNew)
                {
                    Console.WriteLine("应用程序已经在运行");
                    Console.ReadLine();
                    Environment.Exit(0);
                }
                // 注意这有一个细节就是这里不能放到using外面去,因为如果放到using外面去的话,到这里锁资源已经释放了
                // 下次再运行程序的时候永远获取到的都是新锁,所以这里要放里面
                Console.WriteLine("应用程序启动成功!");
                Console.ReadLine();
            }
        }
    }
}

② false的使用场景

在应用程序的初始化阶段不需要同步,但是在后续的操作中需要控制对个某个共享资源的访问.比如多线程日志写入.

 private const string mutexIdForLog = "MutexIdForLog";
	
 private static Mutex mutex = new Mutex(false, mutexIdForLog);
 public static void Log(string message)
 {
     Console.WriteLine("日志写入前请求日志文件资源");
     mutex.WaitOne(); // 显示请求所有权,就是当其他的线程或者是进程释放了互斥体之后,才会获取到它的所有权
     try
     {
         // 执行写入操作
         File.AppendAllText("Log.txt", message + Environment.NewLine);
         Console.WriteLine("日志写入结束,释放资源");
     }
     finally
     {
         mutex.ReleaseMutex(); // 释放互斥锁
     }
 }

这个例子中,互斥体初始设置不拥有所有权(false),日志操作可能不会立即发生,且在应用程序的不同阶段需要多次访问.通过在Log中显示调用WaitOne()来请求所有权,可以灵活地控制何时进行同步写入操作.

true 表示立即同步,false表示延迟同步,各有各自的使用场景.

② EventWaitHandle(事件等待句柄)

EventWaitHandle是一个非常灵活的同步机制,可以用于两个线程间通信,也可以用于两个进程间通信.它的工作原理类似于一个交通信号灯,可以阻止(无信号,非终止等待状态)或者允许(有信号状态,终止等待状态)一个或者多个线程执行.

  • 线程间通信
namespace TestSimple
{
    public class EventWaitHandleSimple
    {
        // 1. 第一个参数false,表示开始创建的时候是有信号,还是无信号,就是如果设置为true,就是一开始会先发一个信号
        // 2. AutoReset 的意思就是是否自动重置,这里是自动重置,什么是自动重置,意思就是清空信号,如果一个信号被接收到了之后
        //     后面有两种处理方式,一个是这个信号继续往后传递,一个是这个信号就中断, AutoReset意思就是自动重置
        public static EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);

        public static void RunTask()
        {
            Task.Run(() =>
            {
                Console.WriteLine("线程1等待信号...");
                ewh.WaitOne(); // 等待信号
                Console.WriteLine("线程1接收到信号...");
            });
            Task.Run(() =>
            {
                Console.WriteLine("线程2等待信号...");
                ewh.WaitOne(); // 等待信号
                Console.WriteLine("线程2接收到信号...");
                ewh.Set(); // 发送信号
            });
        }
    }
}
  • 进程间通信

EventWaitHandle也可以用于不同的进程之间的通信.通过使用具有全局命名空间的中的eventId创建EventWaitHandle,不同的进程可以访问同一个EventWaitHandle对象.这使得一个进程可以等待另外一个进程发出的信号.从而实现进程间的协调和同步.


        public static EventWaitHandle ewhConst = new EventWaitHandle(false, EventResetMode.AutoReset, "GlobalEventIdFioman");
        public static void SendSignal()
        {
            ewhConst.Set();
        }

        public static void ReceiveSignal()
        {
            while (true)
            {
                ewhConst.WaitOne(); // 等待信号
                Console.WriteLine("接收到来自其他进程的信号");
            }
        }

注意,这里我们循环等待信号,发现在重新打开这个程序的时候,有可能是进程A收到的信号,也有可能是进程B收到这个信号,具体是哪个进程收到了这个信号是随机的.比如我们第一次启动的为进程1,第二次同时启动相同的应用为进程2,以此类推,后面每次启动一次应用都会发送一次信号,但是具体这个信号是被进程1接收到了还是进程2,进程3接收到了,都是有可能的.

三. 在WPF应用程序中启动程序的时候检查应用是否已经启动,如果已经启动就将主窗口显示出来

思路就是使用互斥体和事件,每次设备启动就创建一个互斥体,根据互斥体Id,如果发现互斥体是创建的新的就正常运行,如果发现互斥体已经存在了,就发送应用重复启动事件信号,通知已经启动的程序在已经启动的情况下又启动了一次,然后已经启动的程序在接收到这个事件信号之后,就调用回调(具体是通过委托来实现的),通过这个委托,如果发现主窗口在最小化的状态就让其正常状态并且激活显示到前台.

① 单一应用管理类

namespace IdealSelf.Common
{
    public class SingleInstanceManager
    {
        private const string _mutexId = "MutexId-Fioman-2024-04-09-IdealSelf";
        private const string _eventHandleId = "EventId-Fioman-2024-04-09-IdealSelf";
        private static Mutex? _mutex;
        private static EventWaitHandle? _eWaitHandle;

        public static void SingleCheck()
        {
            /*
             * 创建互斥体,互斥体构造方法,各个参数的含义:
             * 1) false/true, 表示立即要想获取当前的资源,如果这个资源已经存在了,就会使用旧的,createNew就返回了false.
             * 2) 互斥体的Id,用来唯一标识一个互斥体,如果Id相同,那么就是同一个互斥体,创建的时候就不会创建新的,
             * createNew就返回了false
             * 3) createNew,是否重新创建了一个互斥体,如果为true,表示重新创建了一个互斥体,如果为false,
             * 表示这个互斥体已经存在,没有重新创建
             */
            _mutex = new Mutex(false, _mutexId, out bool createdNew);
            if (!createdNew)
            {
                // 互斥体已经存在,证明程序已经在运行,这个时候,就去创建EventWaitHanle事件句柄
                // false,表示创建的这个事件句柄一开始是没有信号的, AutoReset表示会自动清空信号
                _eWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, _eventHandleId);
                // 发送信号,发送完信号之后,其实这里应该是给之前的进程发送的信号
                _eWaitHandle.Set();
                // 关闭当前应用
                Environment.Exit(0);
            }
        }

        // 监听程序启动事件,在window加载的时候调用
        public static void AppStartEventListen(Action? showWindow)
        {
            /*
             * 这里为什么要重新开启一个任务,如果不重新开启会造成什么后果?
             * 因为这个函数是UI线程进行调用的,所以它会在UI线程上进行执行,在UI线程上进行执行的时候,
             * while(true)后面的waitOne会阻塞
             * UI线程,所以这里要创建一个后台线程,目的就是为了避免阻塞UI线程
             */
            Task.Run(() =>
            {
                while (true)
                {
                    _eWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, _eventHandleId);
                    _eWaitHandle.WaitOne();
                    showWindow?.Invoke();
                }
            });
        }
    }
}

② 在app.cs中 调用单一应用检测,如果发现应用已经启动就发送应用重复启动事件信号

namespace IdealSelf
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            SingleInstanceManager.SingleCheck();
        }
    }
}

③ 在主窗口的UI后台代码中去调用监听应用启动事件,目的就是一旦接收到了应用重新启动就显示和激活主窗口

namespace IdealSelf.Views
{
    /// <summary>
    /// Interaction logic for MainView.xaml
    /// </summary>
    public partial class MainView : Window
    {
        public MainView()
        {
            InitializeComponent();
            Loaded += MainView_Loaded;
        }

        private void MainView_Loaded(object sender, RoutedEventArgs e)
        {
            SingleInstanceManager.AppStartEventListen(WakeApp);
        }

        // 唤醒当前的应用,这里应为要操作UI,所以要确保是在UI线程上进行执行的
        // 因为AppStartEventListen是重新创建了一个新的线程,所以执行WakeApp的线程并不是UI线程,这里要确保是UI线程执行
        private void WakeApp()
        {
            Dispatcher.Invoke(() =>
            {
                if (WindowState == WindowState.Minimized)
                {
                    WindowState = WindowState.Normal;
                }
                Activate();
            });
        }
    }
}

结论:

自此这个功能算是完成了,这个功能我们主要收获到的点是什么?

  1. 进程线程间通信(EventWaitHandle)
  2. 进程线程间同步(Mutex)
  3. UI线程上不能直接创建循环(while),如果有阻塞事件不局限于循环,需要创建一个新线程来进行操作.
  4. 非UI线程上不能操作UI,比如UI窗口更新,最大化和最小化,关闭窗口等,要使用Dispatcher.Invoke派发UI线程去做这件事

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/549060.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用colab进行yolov5小demo练习

输入一张动物的图片进行目标检测和分类 !pip install yolov5 import torch from PIL import Image from torchvision import transforms from yolov5.models.experimental import attempt_load from yolov5.utils.general import non_max_suppression# 加载YOLOv5模型 device …

PLC远程通信:实现工业自动化的关键技术

在当今高度信息化和自动化的时代&#xff0c;工业领域对于实时数据的准确传输和迅速响应提出了更高要求。而PLC(可编程逻辑控制器)远程通信技术&#xff0c;正是能够实现工业自动化的关键技术之一。 首先&#xff0c;我们需要了解PLC远程通信的原理。PLC作为一种专用计算机控制…

【HormonyOS4+NEXT】TypeScript基础语法详解

&#x1f64b;‍ 一日之际在于晨 ⭐本期内容&#xff1a;TypeScript基础语法详解 &#x1f3c6;系列专栏&#xff1a;鸿蒙HarmonyOS4NEXT&#xff1a;探索未来智能生态新纪元 文章目录 前言变量与类型函数类与接口类&#xff08;Class&#xff09;接口&#xff08;Interface&am…

SD-WAN企业组网:多样化的应用场景

随着企业网络环境的快速发展&#xff0c;SD-WAN技术正成为实现站点间网络互通的关键所在。它不仅支持企业站点对因特网、SaaS云应用和公有云等多种业务的高效访问&#xff0c;更能满足多样化的业务需求。深入探讨SD-WAN的组网应用场景&#xff0c;我们能够发现其广泛的适用性和…

免费打造个人专属的高颜值本地大模型AI助手,无限量使用 Ollama+LobeChat开源工具,在本地运行AI大模型,安全的和AI对话。

文章目录 1、安装ollama2、下载模型3、安装lobechat4、卸载Ollama 1、安装ollama 第一步&#xff0c;首先安装ollama&#xff0c;选择对应系统的安装包 ollama官网地址&#xff1a;https://ollama.com/ 本问是lunix系统上安装ollama&#xff1a; curl -fsSL https://ollama.…

Python对txt文本文件内容进行替换,以便通过Origin进行数据分析

因为要使用Origin进行数据分析&#xff0c;数据集为单行文本逗号隔开&#xff0c;无法直接复制粘贴到Origin中&#xff0c;故为此整理了一下代码&#xff0c;方便后续直接使用。 一、任务需求 有个1.txt文档文件里面是一行数据信息&#xff0c;要将其规整为每行一个数据&…

排序:冒泡排序,直接插入排序,简单选择排序,希尔排序,快速排序,堆排序,二路归并排序

目录 一.冒泡排序 代码如下 冒泡排序时间复杂度分析 二.直接插入排序 直接插入排序时间复杂度分析 直接插入排序优化&#xff1a;折半插入排序 三.简单选择排序 简单选择排序优化&#xff1a;双向选择排序 选择排序时间复杂度 双向选择排序时间复杂度 四.希尔排序 希…

Java反序列化基础-类的动态加载

类加载器&双亲委派 什么是类加载器 类加载器是一个负责加载器类的对象&#xff0c;用于实现类加载的过程中的加载这一步。每个Java类都有一个引用指向加载它的ClassLoader。而数组类是由JVM直接生成的&#xff08;数组类没有对应的二进制字节流&#xff09; 类加载器有哪…

贝锐蒲公英企业路由器X5 Pro:无需专线和IT人员,分钟级异地组网

尽管我们公司规模较小&#xff0c;只有十几个人&#xff0c;但为了确保项目资料的安全&#xff0c;依旧在公司内部自建了文件存储服务器和办公系统。 但是&#xff0c;随着项目数量的增加&#xff0c;大家出差办公的情况也愈发普遍&#xff0c;如何解决远程访问内部系统成了问…

公司聚会计划:最优宾客名单的算法设计与分析

公司聚会计划&#xff1a;最优宾客名单的算法设计与分析 问题描述算法设计C代码实现时间复杂度分析空间复杂度分析结论 在组织公司聚会时&#xff0c;一个重要的考虑因素是如何确保聚会的愉快氛围。在本问题中&#xff0c;公司主席希望在聚会上避免员工及其直接主管同时出席&am…

Python写FTP文件自动传输脚本

FTP&#xff08;File Transfer Protocol&#xff09;是一种用于文件传输的标准协议&#xff0c;当我们需要上传或下载文件时&#xff0c;经常会使用 FTP。如果每天需要上传或下载大量文件&#xff0c;手工操作无疑是一件费时费力的事情。在本篇文章中&#xff0c;我们将向您介绍…

中国建筑模板出口供应商

随着"一带一路"倡议的深入推进,中国基建企业"走出去"的步伐正在加快。与之相应,建筑模板产品作为工程建设的重要材料,其国际化供应也愈发受到重视。在众多建筑模板生产企业中,贵港市能强优品木业有限公司以其卓越的产品质量和丰富的出口经验,成为了国内知名…

sed 字符替换时目标内容包含 特殊字符怎么处理

背景 想写一个自动修改配置的脚本&#xff0c;输入一个 mysql jdbc 的连接路径&#xff0c;然后替换目标配置中的模版内容&#xff0c;明明很简单的一个内容&#xff0c;结果卡在了 & 这个符号上。 & 到底是什么特殊字符呢&#xff1f;结论&#xff1a;它代表要替换的…

GmSSL-3.1.1编译

1.源码下载&#xff1a; 下载地址&#xff1a;https://github.com/guanzhi/GmSSL/releases选择对应版本下载。 ​ 2.选择要下载的源码包&#xff1a; ​ 2.编译&#xff1a; 2.1 windows编译&#xff1a;打开vs命令行&#xff0c;选择想要编译的版本&#xff0c;x86或x64…

大数据、数据架构、推荐冷启动...小红书的 AI 数据新方案都在这个会

伴随着行业数据持续积累&#xff0c;人工智能正加速渗透各类场景&#xff0c;大数据、数据架构和推荐系统等领域&#xff0c;依然是各行各业目之所聚。4 月 19 至 20 日&#xff0c;「DataFunCon 2024 上海站」来袭&#xff01;大会以“数聚垂域&#xff0c;智领未来”为主题…

数据结构——栈(C++实现)

数据结构——栈 什么是栈栈的实现顺序栈的实现链栈的实现 今天我们来看一个新的数据结构——栈。 什么是栈 栈是一种基础且重要的数据结构&#xff0c;它在计算机科学和编程中扮演着核心角色。栈的名称源于现实生活中的概念&#xff0c;如一叠书或一摞盘子&#xff0c;新添加…

AI概念普及-LangChain

文章目录 概念产品架构核心特性核心组件使用场景其他资源开发支持结论Langchain详细介绍LangChain的具体实现原理LangChain如何与其他大型语言模型&#xff08;LLM&#xff09;集成&#xff0c;有哪些具体的接口或协议&#xff1f;LangChain的性能表现和优化策略有哪些&#xf…

由于找不到msvcr120.dll,无法继续执行代码的详细处理方法,教你快修复msvcr120.dll

DLL文件&#xff0c;全称动态链接库文件&#xff0c;在计算机系统中具有重要作用。其中&#xff0c;msvcr120.dll是一个常见的DLL文件&#xff0c;它关联了许多程序和应用的正常运行。本指南将深入解释 msvcr120.dll文件的功能&#xff0c;并阐述如果缺少该文件会引起什么样的问…

Banana Pi开源社区推出BPI-5202开发板,国产龙芯Loongson 2K1000LA

BPI-5202开发板&#xff0c;国产龙芯Loongson 2K1000LA BPI-5202作为单纯的嵌入式通用控制器软硬件开发平台&#xff0c;采用龙芯2K1000LA芯片设计&#xff0c;基本配置中有2个独立MAC以太网端口、2个RS485端口1个RS232端口2个CAN2.0端口&#xff0c;配置灵活&#xff0c;广泛适…

# ABAP SQL 字符串处理-CONCATCAST

经常我都要在ABAP的sql语句中对字符串进行处理&#xff0c;现在就总结一下可以用到的方法 文章目录 字符串处理拼接字段运行结果 填充字符串运行结果 截取字符串 SUBSTRING运行结果 CAST转换类型程序运行结果 CAST 转换成 DATS类型&#xff08;日期&#xff09; 字符串处理 在…