C# 实现单线程异步互斥锁

news/2024/5/19 4:31:45 标签: c#, wpf, 异步编程, mutex, 互斥锁

文章目录

  • 前言
  • 一、异步互斥锁的作用是什么?
    • 示例一、创建和销毁
  • 二、如何实现?
    • 1、标识
      • (1)标识是否锁住
      • (2)加锁
      • (3)解锁
    • 2、异步通知
      • (1)创建对象
      • (2)返回Task
      • (3)通知完成
    • 3、等待队列
      • (1)创建队列
      • (2) 等待加锁
      • (3)加锁成功
  • 三、完整代码
  • 四、使用示例
    • 1、基本用法
    • 2、尝试加锁
    • 3、加锁对比
      • (1)未加锁
      • (2)加锁
  • 总结


前言

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);

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


总结

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


http://www.niftyadmin.cn/n/5336465.html

相关文章

视频增强修复Topaz Video AI

Topaz Video AI是一款强大的视频增强软件&#xff0c;利用人工智能技术对数千个视频进行训练&#xff0c;结合多个输入视频的帧信息来提高素材的分辨率。该软件可将视频的分辨率提高到最高8K&#xff0c;并保持真实的细节和运动一致性。同时&#xff0c;它还能自动修复视频中的…

electron-vite中的ipc通信

1. 概述 再electron中&#xff0c;进程间的通信通过ipcMain和ipcRenderer模块&#xff0c;这些通道是任意和双向的 1.1. 什么是上下文隔离进程 ipc通道是通过预加载脚本绑定到window对象的electron对象属性上的 2. 通信方式 2.1. ipcMain&#xff08;也就是渲染进程向主进…

一篇文章带你了解 什么是u(ustd)带你了解他的前世今生

在数字货币的繁荣世界中&#xff0c;USDT无疑是其中一位重要的角色。它的前世今生&#xff0c;是一个从无到有&#xff0c;从小到大&#xff0c;经历了种种波折和争议的故事。 2014年11月下旬&#xff0c;一个名为Realcoin的注册地为马恩岛和香港的公司决定改变自己的名字&…

ARM多核调度器DSU

1. 背景 从A75开始&#xff0c;ARM提出了一个新的多核心管理系统单元&#xff0c;叫做DSU(DynamIQ Shared Unit)。DSU的核心功能是控制CPU内核&#xff0c;使其成簇Cluster使用&#xff0c;簇内每一个核心可以单独开关、调整频率/电压&#xff0c;能效表现更佳&#xff0c;甚至…

mmdet.structures.DetDataSample 数据结构解析

官方文档 https://mmdetection.readthedocs.io/zh-cn/latest/api.html#mmdet-structures gt_instances(InstanceData): Ground truth of instance annotations. 标注的数据 <InstanceData(META INFORMATIONDATA FIELDSbboxes: tensor([], size(0, 4))labels: tensor([], d…

鸿蒙开发语言ArkTS--Ability中的生命周期

鸿蒙开发语言ArkTS–Ability中的生命周期 在ArkTS语言中&#xff0c;这是一个在ArkTS中定义Ability生命周期的示例。 Ability的生命周期&#xff1a; onCreate(want, launchParam)&#xff1a;在UI创建后执行的函数。在这个阶段&#xff0c;您可以执行初始化操作&#xff0c;…

获取mac地址,内网ip,当前ip位置信息

import json import socket import time import requests from urllib.parse import quoteclass Home_Url():def get_mac(self):from psutil import net_if_addrsmac info net_if_addrs()for k, v in info.items():if k not in [以太网, en0] and 以太网 not in str(k):conti…

Unity -简单键鼠事件

“Test_03” KeyTest 键鼠事件每帧都要监听&#xff0c;要放在Update()中处理 public class KeyTest : MonoBehaviour {// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){// 【鼠标点击事件】 0左键、1右键…