• 单元测试NUnit,mock组件NSubstitute,信号量SemaphoreSlim,异步lock等例子


    public class LockTest
        {
            private IDatabase _database;
            private readonly Random _random = new Random();
            private int _num = 0;
    
            [SetUp]
            public void SetUp()
            {
                _num = 0;
    
                //信号量,同时访问的线程1
                var slim = new SemaphoreSlim(1, 1);
    
                //mock redis的接口
                _database = Substitute.For<IDatabase>();
    
                //mock LockTake 同时只有一个线程可以拿到锁
                //slim.WaitAsync 参数为0时标识直接返回是否拿到锁,不会类似lock阻塞直至其他人释放
                //这里主要mock redis的场景,拿不到要使用循环重复尝试拿锁,因此传0
                //普通异步锁场景根据需求选择等待时间,使用等待时间理论上使用响应时间应比自己写循环简单且有更好的性能
                _database.LockTakeAsync(Arg.Any<RedisKey>(), Arg.Any<RedisValue>(), Arg.Any<TimeSpan>())
                    .Returns(async call => await slim.WaitAsync(call.ArgAt<TimeSpan>(2)));
    
                //mock LockRelease 释放锁
                _database.LockReleaseAsync(Arg.Any<RedisKey>(), Arg.Any<RedisValue>()).Returns(call =>
                {
                    slim.Release();
                    return true;
                });
            }
    
            /// <summary>
            /// 使用锁的版本,_num值会按预期加至5
            /// </summary>
            /// <returns></returns>
            [Test]
            public async Task WithLockTest()
            {
                var tasks = Enumerable.Range(1, 5).Select(c => LockTestTaskInner("1", "1", TimeSpan.FromSeconds(0)));
                await Task.WhenAll(tasks);
                Assert.AreEqual(_num, 5);
            }
    
            /// <summary>
            /// 无锁版本,_num值会配其他线程覆盖
            /// </summary>
            /// <returns></returns>
            [Test]
            public async Task WithoutLockTestTask()
            {
                var tasks = Enumerable.Range(1, 5).Select(async c =>
                {
                    var tmp = _num;
                    //模拟覆盖写
                    await Task.Delay(_random.Next(5));
                    _num = tmp + 1;
                });
                await Task.WhenAll(tasks);
                Console.WriteLine(_num);
                Assert.AreNotEqual(_num, 5);
            }
    
            private async Task LockTestTaskInner(string key, string value, TimeSpan expiry)
            {
                var reTryGetLock = true;
                while (reTryGetLock)
                {
                    var taskLockSuccess = await _database.LockTakeAsync(key, value, expiry);
                    if (taskLockSuccess)
                    {
                        reTryGetLock = false;
                        try
                        {
                            var tmp = _num;
                            //模拟覆盖写
                            await Task.Delay(_random.Next(5));
                            _num = tmp + 1;
                        }
                        finally
                        {
                            await _database.LockReleaseAsync(key, value);
                        }
                    }
                    else
                    {
                        //证明LockTakeAsync不会阻塞
                        //Console.WriteLine("retry");
                        await Task.Delay(_random.Next(10));
                    }
                }
    
            }
  • 相关阅读:
    让网络编程更轻松和有趣 t-io
    设计一个百万级的消息推送系统
    前端安全系列之二:如何防止CSRF攻击
    Maven仓库下载jar包失败的处理方案
    SpringBoot2中配置文件的调整,升级SpringBoot2时候注意的坑
    Table折叠小技巧html-demo
    mysql如何分类统计数量
    前台登录和Token信息交互流程
    windows下安装mysql5.6
    【读书笔记】-- 你不知道的JavaScript
  • 原文地址:https://www.cnblogs.com/wpycs/p/12981778.html
Copyright © 2020-2023  润新知