C# AsyncLock 助手类 异步锁 Nito.AsyncEx

37 阅读1分钟

依赖库

Nito.AsyncEx;

完整代码

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Nito.AsyncEx;

namespace DTcms.Common
{
    public static class AsyncLockHelper
    {
        /// <summary>
        /// 异步锁
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static Task<IDisposable> LockAsync(object key)
        {
            return GetLock(key).LockAsync();
        }

        /// <summary>
        /// 同步锁
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static IDisposable Lock(object key)
        {
            return GetLock(key).Lock();
        }

        /// <summary>
        /// 获取一个异步锁对象
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static AsyncLock GetLock(object key)
        {
            return _locks.GetOrAdd(key, _ => new Lazy<AsyncLock>(() => new AsyncLock())).Value;
        }

        /// <summary>
        /// 手动添加一个异步锁对象
        /// </summary>
        /// <param name="key"></param>
        /// <param name="asyncLock"></param>
        /// <returns></returns>
        public static AsyncLock AddLock(object key, AsyncLock asyncLock)
        {
            return _locks.AddOrUpdate(key, _ => new Lazy<AsyncLock>(() => asyncLock),
                (_, __) => new Lazy<AsyncLock>(() => asyncLock)).Value;
        }

        /// <summary>
        /// 手动添加一个异步锁对象
        /// </summary>
        /// <param name="key"></param>
        /// <param name="asyncLock"></param>
        /// <returns></returns>
        public static AsyncLock AddLock(object key, Func<AsyncLock> createAsyncLock)
        {
            return _locks.AddOrUpdate(key, _ => new Lazy<AsyncLock>(createAsyncLock),
                (_, __) => new Lazy<AsyncLock>(createAsyncLock)).Value;
        }

        public static Lazy<AsyncLock> RemoveLock(object key)
        {
            _locks.TryRemove(key, out var lazy);
            return lazy;
        }
        
        /// <summary>
        /// 异步锁字典
        /// </summary>
        private static ConcurrentDictionary<object, Lazy<AsyncLock>> _locks =
            new ConcurrentDictionary<object, Lazy<AsyncLock>>();
    }
}

并发示例


        // [TestMethod]
        public async Task testWhenAll()
        {
            var sb = new StringBuilder();
            var watch = ValueStopwatch.StartNew();
            await Task.WhenAll(Enumerable.Range(0, 5).Select(async i =>
            {
                await Task.Delay(i * 1000); // 模拟异步操作
                using (await AsyncLockHelper.LockAsync(sb))
                {
                    await Task.Delay(i * 1000); // 模拟异步操作
                    sb.AppendLine($"[{watch.GetElapsedTime()}] i: " + i);
                }
            }));

            var use_time = watch.GetElapsedTime(); // 总耗时
        }

ValueStopWatch.cs

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// https://source.dot.net/#Microsoft.AspNetCore.Mvc.Core/src/Shared/ValueStopwatch/ValueStopwatch.cs

using System;
using System.Diagnostics;

namespace DTcms.Common
{
    public struct ValueStopwatch
    {
#if !NET7_0_OR_GREATER
        private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
#endif

        private readonly long _startTimestamp;

        public readonly DateTime StartTime;

        public bool IsActive => _startTimestamp != 0;

        private ValueStopwatch(long startTimestamp)
        {
            StartTime = DateTime.Now;
            _startTimestamp = startTimestamp;
        }

        public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());

        public TimeSpan GetElapsedTime()
        {
            // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
            // So it being 0 is a clear indication of default(ValueStopwatch)
            if (!IsActive)
            {
                throw new InvalidOperationException(
                    "An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
            }

            var end = Stopwatch.GetTimestamp();

#if !NET7_0_OR_GREATER
            var timestampDelta = end - _startTimestamp;
            var ticks = (long)(TimestampToTicks * timestampDelta);
            return new TimeSpan(ticks);
#else
        return Stopwatch.GetElapsedTime(_startTimestamp, end);
#endif
        }
    }
}