mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 02:24:30 +08:00
feat(concurrency): 添加工业级异步键锁管理器
- 实现了基于键的细粒度异步锁机制 - 提供自动清理未使用锁的功能,避免内存泄漏 - 集成了统计信息收集功能,便于监控和调试 - 支持同步和异步两种锁获取方式 - 实现了锁句柄的自动释放机制 - 添加了完整的单元测试覆盖各种并发场景
This commit is contained in:
parent
11c7bc1457
commit
b37873a67c
@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2025 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
|
||||
namespace GFramework.Core.Abstractions.Concurrency;
|
||||
|
||||
/// <summary>
|
||||
/// 异步键锁管理器接口,提供基于键的细粒度锁机制
|
||||
/// </summary>
|
||||
public interface IAsyncKeyLockManager : IUtility, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取指定键的锁(推荐使用)
|
||||
/// </summary>
|
||||
/// <param name="key">锁键</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>锁句柄,使用 await using 自动释放</returns>
|
||||
ValueTask<IAsyncLockHandle> AcquireLockAsync(string key, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 同步获取指定键的锁(兼容性方法)
|
||||
/// </summary>
|
||||
/// <param name="key">锁键</param>
|
||||
/// <returns>锁句柄,使用 using 自动释放</returns>
|
||||
IAsyncLockHandle AcquireLock(string key);
|
||||
|
||||
/// <summary>
|
||||
/// 获取锁管理器的统计信息
|
||||
/// </summary>
|
||||
/// <returns>统计信息快照</returns>
|
||||
LockStatistics GetStatistics();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前活跃的锁信息(用于调试)
|
||||
/// </summary>
|
||||
/// <returns>键到锁信息的只读字典</returns>
|
||||
IReadOnlyDictionary<string, LockInfo> GetActiveLocks();
|
||||
}
|
||||
30
GFramework.Core.Abstractions/Concurrency/IAsyncLockHandle.cs
Normal file
30
GFramework.Core.Abstractions/Concurrency/IAsyncLockHandle.cs
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2025 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace GFramework.Core.Abstractions.Concurrency;
|
||||
|
||||
/// <summary>
|
||||
/// 异步锁句柄接口,支持 await using 语法
|
||||
/// </summary>
|
||||
public interface IAsyncLockHandle : IAsyncDisposable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 锁的键
|
||||
/// </summary>
|
||||
string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 锁获取时的时间戳(Environment.TickCount64)
|
||||
/// </summary>
|
||||
long AcquiredTicks { get; }
|
||||
}
|
||||
66
GFramework.Core.Abstractions/Concurrency/LockStatistics.cs
Normal file
66
GFramework.Core.Abstractions/Concurrency/LockStatistics.cs
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2025 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace GFramework.Core.Abstractions.Concurrency;
|
||||
|
||||
/// <summary>
|
||||
/// 锁统计信息
|
||||
/// </summary>
|
||||
public readonly struct LockStatistics
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前活跃的锁数量
|
||||
/// </summary>
|
||||
public int ActiveLockCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计获取锁的次数
|
||||
/// </summary>
|
||||
public int TotalAcquired { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计释放锁的次数
|
||||
/// </summary>
|
||||
public int TotalReleased { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计清理的锁数量
|
||||
/// </summary>
|
||||
public int TotalCleaned { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁信息(用于调试)
|
||||
/// </summary>
|
||||
public readonly struct LockInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 锁的键
|
||||
/// </summary>
|
||||
public string Key { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前引用计数
|
||||
/// </summary>
|
||||
public int ReferenceCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后访问时间戳(Environment.TickCount64)
|
||||
/// </summary>
|
||||
public long LastAccessTicks { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 等待队列长度
|
||||
/// </summary>
|
||||
public int WaitingCount { get; init; }
|
||||
}
|
||||
337
GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs
Normal file
337
GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs
Normal file
@ -0,0 +1,337 @@
|
||||
// Copyright (c) 2025 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Concurrency;
|
||||
|
||||
namespace GFramework.Core.Tests.Concurrency;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class AsyncKeyLockManagerTests
|
||||
{
|
||||
[Test]
|
||||
public async Task AcquireLockAsync_Should_ReturnValidHandle()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
|
||||
// Act
|
||||
await using var handle = await manager.AcquireLockAsync("test-key");
|
||||
|
||||
// Assert
|
||||
Assert.That(handle, Is.Not.Null);
|
||||
Assert.That(handle.Key, Is.EqualTo("test-key"));
|
||||
Assert.That(handle.AcquiredTicks, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AcquireLockAsync_WithSameKey_Should_SerializeAccess()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
var executionOrder = new List<int>();
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// Act
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var index = i;
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
await using var handle = await manager.AcquireLockAsync("same-key");
|
||||
executionOrder.Add(index);
|
||||
await Task.Delay(10);
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionOrder.Count, Is.EqualTo(5));
|
||||
Assert.That(executionOrder.Distinct().Count(), Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AcquireLockAsync_WithDifferentKeys_Should_AllowConcurrentAccess()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
var concurrentCount = 0;
|
||||
var maxConcurrent = 0;
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// Act
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var key = $"key-{i}";
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
await using var handle = await manager.AcquireLockAsync(key);
|
||||
var current = Interlocked.Increment(ref concurrentCount);
|
||||
maxConcurrent = Math.Max(maxConcurrent, current);
|
||||
await Task.Delay(50);
|
||||
Interlocked.Decrement(ref concurrentCount);
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
// Assert
|
||||
Assert.That(maxConcurrent, Is.GreaterThan(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Dispose_Should_ReleaseHandle()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
var handle = await manager.AcquireLockAsync("test-key");
|
||||
|
||||
// Act
|
||||
await handle.DisposeAsync();
|
||||
|
||||
// Assert - 应该能再次获取锁
|
||||
await using var handle2 = await manager.AcquireLockAsync("test-key");
|
||||
Assert.That(handle2, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ConcurrentAcquire_Should_NotThrowException()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// Act
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var key = $"key-{i % 10}";
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
await using var handle = await manager.AcquireLockAsync(key);
|
||||
await Task.Delay(1);
|
||||
}));
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotThrowAsync(async () => await Task.WhenAll(tasks));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ConcurrentAcquireSameKey_Should_SerializeAccess()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
var counter = 0;
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// Act
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
await using var handle = await manager.AcquireLockAsync("same-key");
|
||||
var temp = counter;
|
||||
await Task.Delay(1);
|
||||
counter = temp + 1;
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
// Assert
|
||||
Assert.That(counter, Is.EqualTo(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cleanup_Should_RemoveUnusedLocks()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager(
|
||||
cleanupInterval: TimeSpan.FromMilliseconds(100),
|
||||
lockTimeout: TimeSpan.FromMilliseconds(200));
|
||||
|
||||
// Act
|
||||
await using (var handle = await manager.AcquireLockAsync("temp-key"))
|
||||
{
|
||||
// 持有锁
|
||||
}
|
||||
|
||||
// 等待清理
|
||||
await Task.Delay(400);
|
||||
|
||||
var stats = manager.GetStatistics();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TotalCleaned, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cleanup_Should_NotRemoveActiveLocks()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager(
|
||||
cleanupInterval: TimeSpan.FromMilliseconds(100),
|
||||
lockTimeout: TimeSpan.FromMilliseconds(200));
|
||||
|
||||
// Act
|
||||
await using var handle = await manager.AcquireLockAsync("active-key");
|
||||
|
||||
// 等待清理尝试
|
||||
await Task.Delay(400);
|
||||
|
||||
var activeLocks = manager.GetActiveLocks();
|
||||
|
||||
// Assert
|
||||
Assert.That(activeLocks.ContainsKey("active-key"), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetStatistics_Should_ReturnCorrectCounts()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
|
||||
// Act
|
||||
await using (await manager.AcquireLockAsync("key1"))
|
||||
{
|
||||
await using var handle2 = await manager.AcquireLockAsync("key2");
|
||||
var stats = manager.GetStatistics();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TotalAcquired, Is.EqualTo(2));
|
||||
Assert.That(stats.ActiveLockCount, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
var finalStats = manager.GetStatistics();
|
||||
Assert.That(finalStats.TotalReleased, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetActiveLocks_Should_ReturnCurrentLocks()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
|
||||
// Act
|
||||
await using var handle1 = await manager.AcquireLockAsync("key1");
|
||||
await using var handle2 = await manager.AcquireLockAsync("key2");
|
||||
|
||||
var activeLocks = manager.GetActiveLocks();
|
||||
|
||||
// Assert
|
||||
Assert.That(activeLocks.Count, Is.EqualTo(2));
|
||||
Assert.That(activeLocks.ContainsKey("key1"), Is.True);
|
||||
Assert.That(activeLocks.ContainsKey("key2"), Is.True);
|
||||
Assert.That(activeLocks["key1"].ReferenceCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AcquireLockAsync_AfterDispose_Should_ThrowObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
var manager = new AsyncKeyLockManager();
|
||||
manager.Dispose();
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<ObjectDisposedException>(async () => await manager.AcquireLockAsync("test-key"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AcquireLockAsync_WithCancellation_Should_ThrowOperationCanceledException()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// 先获取锁
|
||||
await using var handle = await manager.AcquireLockAsync("test-key", cts.Token);
|
||||
|
||||
// Act
|
||||
await cts.CancelAsync();
|
||||
|
||||
// Assert
|
||||
Assert.CatchAsync<OperationCanceledException>(async () =>
|
||||
await manager.AcquireLockAsync("test-key", cts.Token));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AcquireLock_Sync_Should_Work()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
|
||||
// Act
|
||||
using var handle = manager.AcquireLock("test-key");
|
||||
|
||||
// Assert
|
||||
Assert.That(handle, Is.Not.Null);
|
||||
Assert.That(handle.Key, Is.EqualTo("test-key"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CleanupDuringAcquire_Should_NotCauseRaceCondition()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager(
|
||||
cleanupInterval: TimeSpan.FromMilliseconds(50),
|
||||
lockTimeout: TimeSpan.FromMilliseconds(100));
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// Act - 在清理过程中不断获取和释放锁
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
for (var j = 0; j < 10; j++)
|
||||
{
|
||||
await using var handle = await manager.AcquireLockAsync($"key-{j % 5}");
|
||||
await Task.Delay(10);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotThrowAsync(async () => await Task.WhenAll(tasks));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleDispose_Should_BeSafe()
|
||||
{
|
||||
// Arrange
|
||||
var manager = new AsyncKeyLockManager();
|
||||
|
||||
// Act
|
||||
manager.Dispose();
|
||||
manager.Dispose();
|
||||
|
||||
// Assert - 不应该抛出异常
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HandleDispose_MultipleTimes_Should_BeSafe()
|
||||
{
|
||||
// Arrange
|
||||
using var manager = new AsyncKeyLockManager();
|
||||
var handle = await manager.AcquireLockAsync("test-key");
|
||||
|
||||
// Act
|
||||
await handle.DisposeAsync();
|
||||
await handle.DisposeAsync();
|
||||
handle.Dispose();
|
||||
|
||||
// Assert - 不应该抛出异常
|
||||
Assert.Pass();
|
||||
}
|
||||
}
|
||||
176
GFramework.Core/Concurrency/AsyncKeyLockManager.cs
Normal file
176
GFramework.Core/Concurrency/AsyncKeyLockManager.cs
Normal file
@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2025 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using GFramework.Core.Abstractions.Concurrency;
|
||||
|
||||
namespace GFramework.Core.Concurrency;
|
||||
|
||||
/// <summary>
|
||||
/// 工业级异步键锁管理器,支持自动清理、统计和调试
|
||||
/// </summary>
|
||||
public sealed class AsyncKeyLockManager : IAsyncKeyLockManager
|
||||
{
|
||||
private readonly Timer _cleanupTimer;
|
||||
private readonly ConcurrentDictionary<string, LockEntry> _locks = new();
|
||||
private readonly long _lockTimeoutTicks;
|
||||
private volatile bool _disposed;
|
||||
|
||||
// 统计计数器
|
||||
private int _totalAcquired;
|
||||
private int _totalCleaned;
|
||||
private int _totalReleased;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化锁管理器
|
||||
/// </summary>
|
||||
/// <param name="cleanupInterval">清理间隔,默认 60 秒</param>
|
||||
/// <param name="lockTimeout">锁超时时间,默认 300 秒</param>
|
||||
public AsyncKeyLockManager(TimeSpan? cleanupInterval = null, TimeSpan? lockTimeout = null)
|
||||
{
|
||||
var cleanupIntervalValue = cleanupInterval ?? TimeSpan.FromSeconds(60);
|
||||
var lockTimeoutValue = lockTimeout ?? TimeSpan.FromSeconds(300);
|
||||
_lockTimeoutTicks = (long)(lockTimeoutValue.TotalMilliseconds * TimeSpan.TicksPerMillisecond / 10000);
|
||||
|
||||
_cleanupTimer = new Timer(CleanupUnusedLocks, null, cleanupIntervalValue, cleanupIntervalValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取指定键的锁
|
||||
/// </summary>
|
||||
public async ValueTask<IAsyncLockHandle> AcquireLockAsync(string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var entry = _locks.GetOrAdd(key, _ => new LockEntry());
|
||||
Interlocked.Increment(ref entry.ReferenceCount);
|
||||
entry.LastAccessTicks = System.Environment.TickCount64;
|
||||
|
||||
// 再次检查 disposed(防止在 GetOrAdd 后 Dispose)
|
||||
if (_disposed)
|
||||
{
|
||||
Interlocked.Decrement(ref entry.ReferenceCount);
|
||||
throw new ObjectDisposedException(nameof(AsyncKeyLockManager));
|
||||
}
|
||||
|
||||
await entry.Semaphore.WaitAsync(cancellationToken);
|
||||
|
||||
Interlocked.Increment(ref _totalAcquired);
|
||||
|
||||
return new AsyncLockHandle(this, key, entry, System.Environment.TickCount64);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步获取指定键的锁
|
||||
/// </summary>
|
||||
public IAsyncLockHandle AcquireLock(string key)
|
||||
{
|
||||
return AcquireLockAsync(key).AsTask().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取统计信息
|
||||
/// </summary>
|
||||
public LockStatistics GetStatistics()
|
||||
{
|
||||
return new LockStatistics
|
||||
{
|
||||
ActiveLockCount = _locks.Count,
|
||||
TotalAcquired = _totalAcquired,
|
||||
TotalReleased = _totalReleased,
|
||||
TotalCleaned = _totalCleaned
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取活跃锁信息
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, LockInfo> GetActiveLocks()
|
||||
{
|
||||
return _locks.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => new LockInfo
|
||||
{
|
||||
Key = kvp.Key,
|
||||
ReferenceCount = kvp.Value.ReferenceCount,
|
||||
LastAccessTicks = kvp.Value.LastAccessTicks,
|
||||
WaitingCount = kvp.Value.Semaphore.CurrentCount == 0 ? 1 : 0
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
_cleanupTimer.Dispose();
|
||||
|
||||
// 统一释放所有 semaphore
|
||||
foreach (var entry in _locks.Values)
|
||||
{
|
||||
entry.Dispose();
|
||||
}
|
||||
|
||||
_locks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放指定键的锁
|
||||
/// </summary>
|
||||
internal void ReleaseLock(string key, LockEntry entry)
|
||||
{
|
||||
entry.Semaphore.Release();
|
||||
Interlocked.Decrement(ref entry.ReferenceCount);
|
||||
Interlocked.Increment(ref _totalReleased);
|
||||
entry.LastAccessTicks = System.Environment.TickCount64;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理未使用的锁(不 Dispose semaphore,避免 race condition)
|
||||
/// </summary>
|
||||
private void CleanupUnusedLocks(object? state)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
var now = System.Environment.TickCount64;
|
||||
|
||||
foreach (var (key, entry) in _locks)
|
||||
{
|
||||
// 只检查引用计数和超时,不 Dispose
|
||||
if (entry.ReferenceCount == 0 &&
|
||||
now - entry.LastAccessTicks > _lockTimeoutTicks &&
|
||||
_locks.TryRemove(key, out _))
|
||||
{
|
||||
Interlocked.Increment(ref _totalCleaned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁条目,包含信号量和引用计数
|
||||
/// </summary>
|
||||
internal sealed class LockEntry : IDisposable
|
||||
{
|
||||
public readonly SemaphoreSlim Semaphore = new(1, 1);
|
||||
public long LastAccessTicks = System.Environment.TickCount64;
|
||||
public int ReferenceCount;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Semaphore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
GFramework.Core/Concurrency/AsyncLockHandle.cs
Normal file
54
GFramework.Core/Concurrency/AsyncLockHandle.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2025 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Concurrency;
|
||||
|
||||
namespace GFramework.Core.Concurrency;
|
||||
|
||||
/// <summary>
|
||||
/// 异步锁句柄实现
|
||||
/// </summary>
|
||||
internal sealed class AsyncLockHandle : IAsyncLockHandle
|
||||
{
|
||||
private readonly AsyncKeyLockManager.LockEntry _entry;
|
||||
private readonly string _key;
|
||||
private readonly AsyncKeyLockManager _manager;
|
||||
private int _disposed;
|
||||
|
||||
public AsyncLockHandle(AsyncKeyLockManager manager, string key, AsyncKeyLockManager.LockEntry entry,
|
||||
long acquiredTicks)
|
||||
{
|
||||
_manager = manager;
|
||||
_key = key;
|
||||
_entry = entry;
|
||||
Key = key;
|
||||
AcquiredTicks = acquiredTicks;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
public long AcquiredTicks { get; }
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, 1) == 0)
|
||||
{
|
||||
_manager.ReleaseLock(_key, _entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user