C# 实现单线程异步互斥锁_c#异步线程互锁执行-程序员宅基地

技术标签: c#  .Net  mutex  异步编程  wpf  互斥锁  


前言

C#对异步的支持越来越成熟,async、await简化了代码也提高了可读性,但由于在一段上下文中有了异步操作,意味着这段操作可能会被同时重复调用,如果本身没有被设计可以重复调用的情况下,就很可能会出问题。


一、异步互斥锁的作用是什么?

异步互斥锁的作用是用于确保存在异步操作的上下文同步互斥。可以参考flutter的插件mutex功能与本文基本一样。

示例一、创建和销毁

有创建和销毁两个方法,两个方法中都有异步操作,两个方法可以单独调用,但不可以同时调用。
单线程中连续调用创建和销毁(不在同一个上下文无法用await),如果没有互斥限制有可能出现如下的操作:

创建开始->创建异步操作->消息队列->销毁开始->销毁异步操作->消息队列->销毁完成->消息队列->创建完成

加入异步互斥锁之后

加锁->创建开始->创建完成->解锁
加锁等待->销毁开始->销毁完成->解锁

二、如何实现?

由于操作都是在单线程我们直接用标识+队列就可以实现一个互斥锁。

1、标识

(1)标识是否锁住

bool _lock = false;

(2)加锁

_lock=true;

(3)解锁

_lock=false;

2、异步通知

通过TaskCompletionSource可以实现异步通知

(1)创建对象

var tcs = new TaskCompletionSource();

(2)返回Task

return tcs.Task;

(3)通知完成

tcs.SetResult();

3、等待队列

用一个队列来记录等待加锁的请求。

(1)创建队列

Queue<TaskCompletionSource> _queue = new Queue<TaskCompletionSource>();

(2) 等待加锁

_queue.Enqueue(tcs);

(3)加锁成功

_queue.Dequeue().SetResult();

三、完整代码

/// <summary>
/// 异步锁,非线程锁,只能用于单线程异步环境中。
/// </summary>
class AsyncMutex
{
    
    Queue<TaskCompletionSource> _queue = new Queue<TaskCompletionSource>();
    bool _lock = false;
    /// <summary>
    /// 获取锁
    /// </summary>
    /// <returns>返回Task,await后即进入了锁</returns>
    public Task Acquire()
    {
    
        if (_lock)
        {
    
            var tcs = new TaskCompletionSource();
            _queue.Enqueue(tcs);
            return tcs.Task;
        }
        _lock = true;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 尝试获取锁
    /// 因为是单线程环境,重复调用需要切换上下文,否则是无法成功的。
    /// 比如可以await Task.Delay(30);
    /// </summary>
    /// <returns>是否成功</returns>
    public bool TryAcquire()
    {
    
        if (_lock) return false;
        return _lock = true;
    }
    /// <summary>
    /// 释放锁
    /// </summary>
    public void Release()
    {
    
        if (_queue.Count > 0)
        {
    
            _queue.Dequeue().SetResult();
        }
        else
        {
    
            _lock = false;
        }
    }
}

四、使用示例

1、基本用法

直接加锁

AsyncMutex _mtx = new AsyncMutex();
async void test()
{
    
    await _mtx.Acquire();
    //custom code
    _mtx.Release();
}

2、尝试加锁

加锁成功才执行操作

AsyncMutex _mtx = new AsyncMutex();
void test()
{
    
    if (_mtx.TryAcquire())
    {
    
        //custom code
        _mtx.Release();
    }
}

超时等待

AsyncMutex _mtx = new AsyncMutex();
async void test()
{
    
    //超时等待300ms
    bool isLock = false;
    for (int i = 0; i < 10; i++)
    {
    
        if (isLock = _mtx.TryAcquire()) break;
        await Task.Delay(30);
    }
    if (isLock)
    {
    
        //custom code
        _mtx.Release();
    }
}

3、加锁对比

(1)未加锁

async void test(int num)
{
    
    Console.WriteLine("enter " + num);
    //模拟异步操作
    await Task.Delay(10);
    Console.WriteLine("exit " + num);
}
//.net 6.0
test(1);
test(2);
test(3);

可能出现的组合,效果预览
在这里插入图片描述

(2)加锁

AsyncMutex _mtx = new AsyncMutex();
async void test(int num)
{
    
    await _mtx.Acquire();
    Console.WriteLine("enter " + num);
    //模拟异步操作
    await Task.Delay(10);
    Console.WriteLine("exit " + num);
    _mtx.Release();
}
//.net 6.0
test(1);
test(2);
test(3);

效果预览
在这里插入图片描述


总结

以上就是今天要讲的内容,本文简单的实现了单线程的异步互斥锁,实现起来相对简单,但作用还是比较大的。虽然说有些情况的异步是可以在前期设计上避免同时调用,比如登录按钮点击后出现蒙板不允许再次点击,但是对于已存在的代码出现了同时调用问题,此时有互斥锁则可以避免大范围改动代码,有效解决问题。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013113678/article/details/135641028

智能推荐

方舟编译器最新技术细节,2024年最新简短的面试总结-程序员宅基地

文章浏览阅读217次,点赞5次,收藏3次。希望本文对你有所启发,有任何面试上的建议也欢迎留言分享给大家。好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。好了~如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。

李宏毅机器学习课程笔记——回归_李宏毅机器学习中回归课堂笔记-程序员宅基地

文章浏览阅读259次。1、回归是什么回归(Regression) 就是找到一个函数 function,通过输入特征 x,输出一个数值 Scalar。比较像我们常见的函数拟合。2、回归的步骤step1:模型假设,选择模型框架step2:模型评估,如何判断众多模型的好坏(损失函数)step3:模型优化,如何筛选最优的模型(梯度下降)下面以李宏毅老师的课程中的宝可梦能力值的例子来对每个步骤进行分析。2.1 模型选择这里我们都选择线性模型进行分析。2.1.1 单个特征当选取单个特征时,以特征xcpx_{cp}x_李宏毅机器学习中回归课堂笔记

HOG算法及其改进 (附代码)_hog算法代码-程序员宅基地

文章浏览阅读1.2k次,点赞30次,收藏25次。HOG算法及其改进_hog算法代码

RAC 更改主机名及IP地址_crsctl pin css -n-程序员宅基地

文章浏览阅读1.9k次。由于安装RAC时脚本执行顺序错了,导致实例orcl1装到了rac2节点上,orcl2装到了rac1节点上,看起来很别扭,趁这个机会练习下更改主机名和IP地址。原IP及主机名设置:#public IP172.12.1.11 rac1.oracle.com rac1172.12.1.12 rac2.oracle.com rac2#private IP10.10.10.1 rac1_crsctl pin css -n

吴恩达2022机器学习专项课程(一) 1.2 机器学习的应用-程序员宅基地

文章浏览阅读328次,点赞9次,收藏4次。课程将教授哪些内容?为什么机器学习如此重要?机器学习算法的实际应用有哪些?如何提升机器学习算法的性能?人工智能和机器学习的未来趋势是什么?为什么现在是学习机器学习的好时机?

Android组件内核之Service内核原理(三)-程序员宅基地

文章浏览阅读841次,点赞15次,收藏28次。针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!Android前沿技术大纲全套体系化高级架构视频Android高级架构资料、源码、笔记、视频。

随便推点

dB(分贝)定义及其应用(音量 dB dBA 计算 调整)-程序员宅基地

文章浏览阅读7.5k次,点赞7次,收藏18次。音量 分贝 dB dBA 计算 调整理解dB的第一个要点,就是要知道它是表示两个具有相同单位的同一种物理量的相对关系。即两种电或声功率之比,或两种电压或电流值或类似声量之比。它还是一种测量声音相对响度的单位。最初在电话工程领域,dB是用于定义于表示两个功率比,是P1/P0的比值再取以10为底的对数,再乘以10,其数学式为:dB是一个比值,是一个数值,是一个纯计数方法,没有任何单位标注。由于它在不同领域有着不同的名称,因此它也代表不同的实际意义。常见的领域有:声音、信号、增益等。_db

url中%E6%98转换为中文-程序员宅基地

文章浏览阅读1.8k次。通过parse.unquote()方法进行解码,把 URL编码字符串,转换回原先字符串import urllibdata=urllib.parse.unquote(data)_%e6%b8%b8%e7%88%b1%e7%bd%91%e7%bb%9c 转中文

从C语言来理解文件系统_c语言 fgets与while结合(1),洞悉MySQL底层架构-程序员宅基地

文章浏览阅读982次,点赞24次,收藏19次。因此,可以约定一个更节省空间的格式来表示一个 256 色的图像,此种文件格式的描述如下:文件中的第 0 和第 1 个字节是整数 n,代表图像的宽度(2 字节的 n 的取值范围是 0~65 535,说明图像最多只能是 65 535 个像素宽),第 2 和第 3 个字节代表图像的高度。上面的箭头表示的区域就相当是一个输入流,红色的地方相当于一个开关,这个开关可以控制往深绿色区域(标注的是缓冲区)里放进去的数据,输入20个字节的数据只往缓冲区中放进去了10个字节,剩下的10个字节的数据就被停留在了输入流里!

GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点_标记清除算法,复制算法,整理算法-程序员宅基地

文章浏览阅读4.7k次。一、什么是GCJava GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,在使用JAVA的时候,一般不需要专门编写内存回收和垃圾清理代码。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。二、关于GC的三种算法1、复制算法(Copying):在JVM的堆内存当中,新生代使用的是复制算法。新生代的内存区域又分成Eden、Fro..._标记清除算法,复制算法,整理算法

红米独立后该如何与荣耀的竞争?-程序员宅基地

文章浏览阅读320次。在1月10日小米举行的红米Redmi发布会上,其创始人兼董事长雷军喊出了“生死看淡、不服就干”的口号,目标对准无疑就是华为的荣耀,其实回顾荣耀这几年的发展很显然就是跟随小..._红米独立原因

「PHP系列」PHP echo/print语句、数据类型详解_php中echo和print都用于在( )输出数据,而<?=……?>常用于给( )进行赋值。-程序员宅基地

文章浏览阅读1.6k次,点赞39次,收藏25次。echo/print语句、数据类型_php中echo和print都用于在( )输出数据,而常用于给( )进行赋值。