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.mzph.cn/news/802692.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯——求和

题目 给定 n 个整数 a1, a2&#xff0c;…,an&#xff0c;求它们两两相乘再相加的和即: Sa1a2a1a3a1ana2a3 a&#xff08;n-2&#xff09;*an...a(n-1)*an 输入格式 输入的第一行包含一个整数 n。 第二行包含 几 个整数 a1,a2,,an。 输出格式 输出一个整数 S&#xff0c;表示所…

蓝桥杯 2022 省 B 洛谷 P8787 砍竹子

[蓝桥杯 2022 省 B] 砍竹子 题目描述 这天,小明在砍竹子,他面前有 n n n 棵竹子排成一排,一开始第 i i i 棵竹子的高度为 h i h_{i} hi​. 他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为 H H H,那…

全球IP数据库:多维度的数据收集与应用

随着互联网的普及和信息技术的飞速发展&#xff0c;全球IP数据库作为一种重要的数据资源&#xff0c;正在被广泛应用于各个领域。全球IP数据库不仅包含了庞大的IP地址信息&#xff0c;还涵盖了丰富的多维度数据&#xff0c;这些数据可以帮助企业、政府和研究机构更好地了解用户…

并发学习26--多线程 异步模式之工作线程

定义&#xff1a;让有限的工作线程&#xff08;Worker Thread&#xff09;来轮流异步处理无限多的任务。线程池便是这种模式。 饥饿&#xff1a; 固定大小线程池也会有饥饿现象。 若一个线程池有两个线程&#xff0c;能够处理两种任务。但是两种任务间有先后顺序。若来一个任…

css设置主题变量

js设置css变量 document.getElementsByTagName(body)[0].style.setProperty(--theme-color, #5ECB90)js获取css变量 document.getElementsByTagName(body)[0].style.getPropertyValue(--theme-color)css设置css变量 //一般都用:root设置:root {--blue: #1e90ff;--white: #fffff…

比特币挖矿与共识

挖矿是增加比特币货币供应的一个过程。挖矿同时还保护着比特币系统的安全&#xff0c;防止欺诈交易&#xff0c;避免“双重支付”&#xff0c;“双重支付”是指多次花费同一笔比特币。矿工们通过为比特币网络提供算力来换取获得比特币奖励的机会。 矿工们验证每笔新的交易并把…

蓝桥杯每日一题:奶牛选美(DSF)

听说最近两斑点的奶牛最受欢迎&#xff0c;约翰立即购进了一批两斑点牛。 不幸的是&#xff0c;时尚潮流往往变化很快&#xff0c;当前最受欢迎的牛变成了一斑点牛。 约翰希望通过给每头奶牛涂色&#xff0c;使得它们身上的两个斑点能够合为一个斑点&#xff0c;让它们能够更…

用户画像——集群搭建

用户画像——集群搭建 1.设计一个规模合适的集群 1.1.1资源预估 1.1.2 选择服务器 2.部署和管理集群的工具 2.1 Hadoop的发展历程 2.2 部署和管理Hadoop的集群并不简单 三种工具的部署方式 3.自动创建虚拟机 3.1 什么是Vagrant&#xff1f; 3.2 安装Vagrant和概念介绍 3.3 使用…

day 1 将go基础知识复习一下

本文章主要是写自己在做这个项目时候遇到的一些困难&#xff0c;如果都是做这个项目的&#xff08;后端&#xff09;&#xff0c;可以看看 这个是项目网址 gin-vue-admin : https://github.com/flipped-aurora/gin-vue-admin 在此表示对大神奇淼的敬佩 首先&#xff0c;我们…

R-tree

R-tree 是一种空间访问方法的数据结构&#xff0c;用于有效地存储和检索多维空间数据&#xff0c;例如地理坐标、矩形或多边形。它特别适用于处理在空间数据库中常见的空间查询&#xff0c;例如最近邻查询、空间连接和空间范围查询。 R-tree 的设计目的是处理大量空间对象&…

在Windows系统上下载并安装MySQL的详细教程

在这篇教程中&#xff0c;介绍如何在Windows系统上下载并安装MySQL。以下是步骤&#xff1a; 1. 访问MySQL官方网站&#xff1a;https://www.mysql.com/ 2. 在主页上&#xff0c;向下滚动到“Developer Zone”&#xff0c;然后单击“MySQL Community (GPL) Downloads”。 3.…

c++——sort()函数

一、代码和效果 #include<bits/stdc.h> using namespace std;int main() {int a[6]{1,45,2,5,456,7};sort(a,a6);for(int i0; i<6; i){cout<<a[i]<<" "<<endl;}return 0; } 二、sort函数解析 &#xff08;从小到大&#xff09; std::so…

深入理解k8s kube-proxy

1、概述 我觉得只要大家知道kube-proxy是用来配置网络规则的而不是转发流量的&#xff0c;真正的流量由iptables/ipvs来转发就可以了。 网络是k8s的一个关键部分。理解k8s中网络组件如何工作可以帮助更好的设计和配置我们的应用。 kube-proxy就是K8s网络的核心组件。它把我们…

Vue3有哪些常用的API

Vue3提供了许多常用的API&#xff0c;这些API可以帮助开发者更高效地构建和管理Vue应用。以下是一些Vue3中常用的API及其功能描述&#xff1a; ref&#xff1a;ref函数用于创建一个响应式引用。它接受一个初始值&#xff0c;并返回一个响应式的对象&#xff0c;该对象的value属…

C# 系统学习(实例计算器)

下面是一个使用 C# 编写的简易计算器的示例代码。这个计算器将支持加、减、乘、除四种基本运算。 using System;class Calculator {static void Main(string[] args){Console.WriteLine("欢迎使用简易计算器&#xff01;");while (true){Console.WriteLine("请…

从“危”到“机”:HubSpot如何助企业转化出海营销CRM风险?

在全球化的大背景下&#xff0c;越来越多的企业选择出海拓展业务&#xff0c;以寻求更大的发展空间。然而&#xff0c;随着市场的扩大&#xff0c;企业在出海营销过程中也面临着各种风险。为了有效规避这些风险&#xff0c;许多企业选择借助HubSpot这样的专业营销软件。今天运营…

国际数字影像产业园构建成都文创产业园新地标!

国际数字影像产业园区&#xff0c;位于成都市金牛区的核心地带&#xff0c;不仅地理位置得天独厚&#xff0c;而且周边配套设施完善&#xff0c;交通便捷&#xff0c;不止成为北二环的新地标&#xff0c;正在构建成都文创产业园新地标&#xff01; 国际数字影像产业园这一片区…

专利年费缴纳后电子票据

专利年费缴纳后的电子票据 随着信息技术的快速发展&#xff0c;电子票据作为一种新型的支付与记录方式&#xff0c;已经深入到了我们日常生活的各个方面。特别是在知识产权领域&#xff0c;专利年费的缴纳也开始逐步实现电子化。 一、专利年费缴纳的重要性 专利年费是专利权…

Windows本地Clion运行CUDA程序

先要条件 在本地安装Visual Studio并且安装好CUDA。建议先安装VS而不是CUDA。如果先安装CUDA&#xff0c;安装遇到问题可参考&#xff1a;https://blog.csdn.net/Cony_14/article/details/137510909 关键步骤 CLion中创建CUDA项目 左上角-新建-项目-CUDA可执行文件。 针对…

5【PS让图片动起来】系列3-【时间轴 花瓣飘落】

【问题介绍】上章将花瓣拆分为一片片花瓣&#xff0c;现在让这片花瓣【动态】飘落吧~ PS选择“窗口”→ “时间轴” 下方弹出时间轴&#xff0c;其中01:00f就是这个位置为1秒的。中间的0 10f 20f等&#xff0c;为帧速率&#xff0c;也就是一秒的动画时间里有几帧的意思 下图…