GFramework/GFramework.Core/Concurrency/AsyncKeyLockManager.cs
GeWuYou b37873a67c feat(concurrency): 添加工业级异步键锁管理器
- 实现了基于键的细粒度异步锁机制
- 提供自动清理未使用锁的功能,避免内存泄漏
- 集成了统计信息收集功能,便于监控和调试
- 支持同步和异步两种锁获取方式
- 实现了锁句柄的自动释放机制
- 添加了完整的单元测试覆盖各种并发场景
2026-03-10 23:11:35 +08:00

176 lines
5.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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();
}
}
}