From 378d7afb23cb21fa554c78c0d5e9575a330e2f8a Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Wed, 11 Mar 2026 19:17:03 +0800
Subject: [PATCH] =?UTF-8?q?fix(concurrency):=20=E4=BF=AE=E5=A4=8D=E5=BC=82?=
=?UTF-8?q?=E6=AD=A5=E9=94=81=E7=AE=A1=E7=90=86=E5=99=A8=E4=B8=AD=E7=9A=84?=
=?UTF-8?q?=E8=B6=85=E6=97=B6=E5=8D=95=E4=BD=8D=E9=94=99=E8=AF=AF=E5=92=8C?=
=?UTF-8?q?=E8=B5=84=E6=BA=90=E6=B3=84=E6=BC=8F=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修正 _lockTimeoutTicks 为 _lockTimeoutMs,统一使用毫秒作为时间单位
- 在 AcquireLockAsync 方法中添加异常处理,防止锁等待失败时的资源泄漏
- 为同步 AcquireLock 方法添加 ConfigureAwait(false) 避免死锁风险
- 更新锁统计信息中的 WaitingCount 字段为近似值说明
- 修正清理逻辑中使用正确的超时单位进行比较
---
.../Concurrency/LockStatistics.cs | 5 ++-
.../Concurrency/AsyncKeyLockManager.cs | 34 +++++++++++++------
2 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/GFramework.Core.Abstractions/Concurrency/LockStatistics.cs b/GFramework.Core.Abstractions/Concurrency/LockStatistics.cs
index a748db3..c875496 100644
--- a/GFramework.Core.Abstractions/Concurrency/LockStatistics.cs
+++ b/GFramework.Core.Abstractions/Concurrency/LockStatistics.cs
@@ -60,7 +60,10 @@ public readonly struct LockInfo
public long LastAccessTicks { get; init; }
///
- /// 等待队列长度
+ /// 等待队列长度(近似值)
+ /// 注意:这是一个基于 SemaphoreSlim.CurrentCount 的近似指示器,
+ /// 当 CurrentCount == 0 时表示锁被持有且可能有等待者,返回 1;
+ /// 否则返回 0。这不是精确的等待者数量,仅用于调试参考。
///
public int WaitingCount { get; init; }
}
\ No newline at end of file
diff --git a/GFramework.Core/Concurrency/AsyncKeyLockManager.cs b/GFramework.Core/Concurrency/AsyncKeyLockManager.cs
index 7872482..9ad2384 100644
--- a/GFramework.Core/Concurrency/AsyncKeyLockManager.cs
+++ b/GFramework.Core/Concurrency/AsyncKeyLockManager.cs
@@ -23,7 +23,7 @@ public sealed class AsyncKeyLockManager : IAsyncKeyLockManager
{
private readonly Timer _cleanupTimer;
private readonly ConcurrentDictionary _locks = new();
- private readonly long _lockTimeoutTicks;
+ private readonly long _lockTimeoutMs;
private volatile bool _disposed;
// 统计计数器
@@ -40,7 +40,7 @@ public sealed class AsyncKeyLockManager : IAsyncKeyLockManager
{
var cleanupIntervalValue = cleanupInterval ?? TimeSpan.FromSeconds(60);
var lockTimeoutValue = lockTimeout ?? TimeSpan.FromSeconds(300);
- _lockTimeoutTicks = (long)(lockTimeoutValue.TotalMilliseconds * TimeSpan.TicksPerMillisecond / 10000);
+ _lockTimeoutMs = (long)lockTimeoutValue.TotalMilliseconds;
_cleanupTimer = new Timer(CleanupUnusedLocks, null, cleanupIntervalValue, cleanupIntervalValue);
}
@@ -63,19 +63,32 @@ public sealed class AsyncKeyLockManager : IAsyncKeyLockManager
throw new ObjectDisposedException(nameof(AsyncKeyLockManager));
}
- await entry.Semaphore.WaitAsync(cancellationToken);
-
- Interlocked.Increment(ref _totalAcquired);
-
- return new AsyncLockHandle(this, key, entry, System.Environment.TickCount64);
+ try
+ {
+ await entry.Semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ Interlocked.Increment(ref _totalAcquired);
+ return new AsyncLockHandle(this, key, entry, System.Environment.TickCount64);
+ }
+ catch
+ {
+ // 如果等待失败(取消或异常),递减引用计数以防止泄漏
+ Interlocked.Decrement(ref entry.ReferenceCount);
+ throw;
+ }
}
///
- /// 同步获取指定键的锁
+ /// 同步获取指定键的锁(同步阻塞调用,优先使用 AcquireLockAsync)
///
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时,作为低级逃生口(escape hatch)使用。
+ /// 如果可能,请优先使用 。
+ ///
public IAsyncLockHandle AcquireLock(string key)
{
- return AcquireLockAsync(key).AsTask().GetAwaiter().GetResult();
+ // 使用 ConfigureAwait(false) 以避免在具有同步上下文的环境中捕获上下文,降低死锁风险
+ return AcquireLockAsync(key).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -104,6 +117,7 @@ public sealed class AsyncKeyLockManager : IAsyncKeyLockManager
Key = kvp.Key,
ReferenceCount = kvp.Value.ReferenceCount,
LastAccessTicks = kvp.Value.LastAccessTicks,
+ // CurrentCount == 0 表示锁被持有,可能有等待者(近似值)
WaitingCount = kvp.Value.Semaphore.CurrentCount == 0 ? 1 : 0
});
}
@@ -151,7 +165,7 @@ public sealed class AsyncKeyLockManager : IAsyncKeyLockManager
{
// 只检查引用计数和超时,不 Dispose
if (entry.ReferenceCount == 0 &&
- now - entry.LastAccessTicks > _lockTimeoutTicks &&
+ now - entry.LastAccessTicks > _lockTimeoutMs &&
_locks.TryRemove(key, out _))
{
Interlocked.Increment(ref _totalCleaned);