diff --git a/src/EasilyNET.Core/Threading/AsyncLock.cs b/src/EasilyNET.Core/Threading/AsyncLock.cs index 3337e096..b855e9b4 100644 --- a/src/EasilyNET.Core/Threading/AsyncLock.cs +++ b/src/EasilyNET.Core/Threading/AsyncLock.cs @@ -8,7 +8,6 @@ namespace EasilyNET.Core.Threading; public sealed class AsyncLock { private readonly Task _release; - private readonly AsyncSemaphore _semaphore = new(); /// @@ -20,6 +19,7 @@ public AsyncLock() } /// + /// 获取内部信号量的占用状态 /// /// public int GetSemaphoreTaken() => _semaphore.GetTaken(); @@ -31,7 +31,7 @@ public AsyncLock() public int GetQueueCount() => _semaphore.GetQueueCount(); /// - /// 锁定,返回一个 对象。 + /// 锁定,返回一个 对象 /// /// public Task LockAsync() @@ -41,23 +41,43 @@ public Task LockAsync() } /// - /// 锁定任务的执行。 + /// 锁定任务的执行,无返回值 + /// + /// + /// + public async Task LockAsync(Func taskFunc) + { + using (await LockAsync()) + { + await taskFunc(); + } + } + + /// + /// 锁定任务的执行,可返回执行函数的结果 /// - /// + /// /// - public async Task LockAsync(Task task) + // ReSharper disable once UnusedMethodReturnValue.Global + public async Task LockAsync(Func> taskFunc) { - using var r = LockAsync(); - await task; + using (await LockAsync()) + { + var t = await taskFunc(); + return t; + } } /// - /// 释放者 + /// Release /// /// public readonly struct Release(AsyncLock asyncLock) : IDisposable { /// - public void Dispose() => asyncLock._semaphore.Release(); + public void Dispose() + { + asyncLock._semaphore.Release(); + } } } \ No newline at end of file diff --git a/src/EasilyNET.Core/Threading/SyncLock.cs b/src/EasilyNET.Core/Threading/SyncLock.cs new file mode 100644 index 00000000..da74ffd8 --- /dev/null +++ b/src/EasilyNET.Core/Threading/SyncLock.cs @@ -0,0 +1,74 @@ +// ReSharper disable UnusedMember.Global + +namespace EasilyNET.Core.Threading; + +/// +/// ͬ,ڽһ̷߳ʹԴ +/// +public sealed class SyncLock +{ + private readonly SyncSemaphore _semaphore = new(); + private int _ownerThreadId = -1; + + /// + /// ȡڲźռ״̬ + /// + /// + public int GetSemaphoreTaken() => _semaphore.GetTaken(); + + /// + /// ȡڲźĶм + /// + /// + public int GetQueueCount() => _semaphore.GetQueueCount(); + + /// + /// ,һ + /// + /// + public Release Lock() + { + var currentThreadId = Environment.CurrentManagedThreadId; + if (_ownerThreadId == currentThreadId) + { + throw new InvalidOperationException("Reentrant lock detected"); + } + _semaphore.Wait(); + _ownerThreadId = currentThreadId; + return new(this); + } + + /// + /// ִ,޷ֵ + /// + /// + public void Lock(Action action) + { + using var r = Lock(); + action(); + } + + /// + /// ִ,ɷִкĽ + /// + /// + public T Lock(Func action) + { + using var r = Lock(); + return action(); + } + + /// + /// Release + /// + /// + public readonly struct Release(SyncLock syncLock) : IDisposable + { + /// + public void Dispose() + { + syncLock._ownerThreadId = -1; + syncLock._semaphore.Release(); + } + } +} \ No newline at end of file diff --git a/src/EasilyNET.Core/Threading/SyncSemaphore.cs b/src/EasilyNET.Core/Threading/SyncSemaphore.cs new file mode 100644 index 00000000..8f806d1f --- /dev/null +++ b/src/EasilyNET.Core/Threading/SyncSemaphore.cs @@ -0,0 +1,57 @@ +using System.Collections.Concurrent; + +namespace EasilyNET.Core.Threading; + +/// +/// ͬź +/// +internal sealed class SyncSemaphore +{ + private readonly ConcurrentQueue _waiters = []; + private int _isTaken; + + /// + /// ȡǷռ + /// + /// + public int GetTaken() => _isTaken; + + /// + /// ȡȴ + /// + /// + public int GetQueueCount() => _waiters.Count; + + /// + /// ͬȴ + /// + public void Wait() + { + // _isTaken ֵ 0Ϊ 1ء + if (Interlocked.CompareExchange(ref _isTaken, 1, 0) == 0) + { + return; + } + // _isTaken ֵ 0һµ ManualResetEventSlimΪδֹ״̬ + var mre = new ManualResetEventSlim(false); + // ManualResetEventSlim ʵӵȴС + _waiters.Enqueue(mre); + // ȴ ManualResetEventSlim ֹ + mre.Wait(); + } + + /// + /// ͷ + /// + public void Release() + { + if (_waiters.TryDequeue(out var toRelease)) + { + toRelease.Set(); + } + else + { + Interlocked.Exchange(ref _isTaken, 0); + } + } +} \ No newline at end of file diff --git a/test/EasilyNET.Test.Unit/Threading/AsyncLockTest.cs b/test/EasilyNET.Test.Unit/Threading/AsyncLockTest.cs index b07f08c2..367e83fb 100644 --- a/test/EasilyNET.Test.Unit/Threading/AsyncLockTest.cs +++ b/test/EasilyNET.Test.Unit/Threading/AsyncLockTest.cs @@ -3,11 +3,8 @@ namespace EasilyNET.Test.Unit.Threading; -/// -/// 测试异步锁 -/// [TestClass] -public class AsyncLockTest(TestContext testContext) +public class AsyncLockTests(TestContext testContext) { // ReSharper disable once CollectionNeverQueried.Local private static readonly Dictionary _dictionary = []; @@ -115,4 +112,83 @@ public async Task WaitingTasksAreReleasedWhenSemaphoreIsReleased() res2.Dispose(); //释放 asyncLock.GetSemaphoreTaken().Should().Be(0); } + + /// + /// 测试基本锁定和释放功能,确保 _isTaken 状态正确。 + /// + [TestMethod] + public async Task LockAsync_ShouldLockAndRelease() + { + var asyncLock = new AsyncLock(); + Assert.AreEqual(0, asyncLock.GetSemaphoreTaken()); + using (await asyncLock.LockAsync()) + { + Assert.AreEqual(1, asyncLock.GetSemaphoreTaken()); + } + Assert.AreEqual(0, asyncLock.GetSemaphoreTaken()); + } + + /// + /// 测试 LockAsync(Action action) 方法,确保传入的操作被执行。 + /// + [TestMethod] + public async Task LockAsync_ShouldExecuteAction() + { + var asyncLock = new AsyncLock(); + var executed = false; + await asyncLock.LockAsync(() => Task.Run(() => executed = true)); + Assert.IsTrue(executed); + } + + /// + /// 测试在锁定时,后续任务会排队等待,确保任务按顺序执行。 + /// + [TestMethod] + public async Task LockAsync_ShouldQueueWhenLocked() + { + var asyncLock = new AsyncLock(); + var task1Completed = false; + var task2Completed = false; + var task1 = Task.Run(async () => + { + using (await asyncLock.LockAsync()) + { + await Task.Delay(100); + task1Completed = true; + } + }); + var task2 = Task.Run(async () => + { + using (await asyncLock.LockAsync()) + { + task2Completed = true; + } + }); + await Task.WhenAll(task1, task2); + Assert.IsTrue(task1Completed); + Assert.IsTrue(task2Completed); + } + + /// + /// 测试多线程环境下的锁定行为,确保计数器正确增加。 + /// + [TestMethod] + public async Task LockAsync_ShouldHandleMultipleThreads() + { + var asyncLock = new AsyncLock(); + var counter = 0; + var tasks = new Task[10]; + for (var i = 0; i < tasks.Length; i++) + { + tasks[i] = Task.Run(async () => + { + for (var j = 0; j < 100; j++) + { + await asyncLock.LockAsync(async () => await Task.Run(() => counter++)); + } + }); + } + await Task.WhenAll(tasks); + Assert.AreEqual(1000, counter); + } } \ No newline at end of file diff --git a/test/EasilyNET.Test.Unit/Threading/SyncLockTest.cs b/test/EasilyNET.Test.Unit/Threading/SyncLockTest.cs new file mode 100644 index 00000000..e08aac58 --- /dev/null +++ b/test/EasilyNET.Test.Unit/Threading/SyncLockTest.cs @@ -0,0 +1,105 @@ +using EasilyNET.Core.Threading; + +namespace EasilyNET.Test.Unit.Threading; + +[TestClass] +public class SyncLockTest +{ + /// + /// 测试基本锁定和释放功能,确保 _isTaken 状态正确。 + /// + [TestMethod] + public void Lock_ShouldLockAndRelease() + { + var syncLock = new SyncLock(); + Assert.AreEqual(0, syncLock.GetSemaphoreTaken()); + using (syncLock.Lock()) + { + Assert.AreEqual(1, syncLock.GetSemaphoreTaken()); + } + Assert.AreEqual(0, syncLock.GetSemaphoreTaken()); + } + + /// + /// 测试 Lock(Action action) 方法,确保传入的操作被执行。 + /// + [TestMethod] + public void Lock_ShouldExecuteAction() + { + var syncLock = new SyncLock(); + var executed = false; + syncLock.Lock(() => executed = true); + Assert.IsTrue(executed); + } + + /// + /// 测试在锁定时,后续任务会排队等待,确保任务按顺序执行。 + /// + [TestMethod] + public void Lock_ShouldQueueWhenLocked() + { + var syncLock = new SyncLock(); + var task1Completed = false; + var task2Completed = false; + var task1 = Task.Run(() => + { + using (syncLock.Lock()) + { + Thread.Sleep(100); + task1Completed = true; + } + }); + var task2 = Task.Run(() => + { + using (syncLock.Lock()) + { + task2Completed = true; + } + }); + Task.WaitAll(task1, task2); + Assert.IsTrue(task1Completed); + Assert.IsTrue(task2Completed); + } + + /// + /// 测试多线程环境下的锁定行为,确保计数器正确增加。 + /// + [TestMethod] + public void Lock_ShouldHandleMultipleThreads() + { + var syncLock = new SyncLock(); + var counter = 0; + var tasks = new Task[10]; + for (var i = 0; i < tasks.Length; i++) + { + tasks[i] = Task.Run(() => + { + for (var j = 0; j < 100; j++) + { + syncLock.Lock(() => counter++); + } + }); + } + Task.WaitAll(tasks); + Assert.AreEqual(1000, counter); + } + + /// + /// 测试锁的重入行为,确保锁不允许重入。 + /// + [TestMethod] + public void Lock_ShouldNotAllowReentrancy() + { + var syncLock = new SyncLock(); + var reentrancyDetected = false; + try + { + syncLock.Lock(() => syncLock.Lock(() => true)); + } + catch (InvalidOperationException) + { + reentrancyDetected = true; + } + Assert.IsTrue(reentrancyDetected); + } +} \ No newline at end of file