不能直接用 lock 做异步临界区保护,因为 lock 是同步原语,会阻塞线程,而 await 可能导致线程切换,使后续代码在其他线程执行,锁提前释放,引发竞态或死锁;AsyncLock 基于 SemaphoreSlim 实现,支持异步等待且需正确初始化、释放与隔离使用。

为什么不能直接用 lock 做异步临界区保护
因为 lock 是同步原语,它会阻塞线程;而 await 可能导致线程切换,一旦在 lock 块里 await,后续代码可能在另一个线程上执行,此时锁早已释放——lock 完全失效,还可能引发死锁或竞态。常见错误现象是:看似加了锁,但并发写入仍发生,尤其在 Task.Run 或 I/O await 后。
AsyncLock 的核心实现靠 SemaphoreSlim
SemaphoreSlim 支持异步等待(WaitAsync),且可设初始计数为 1,正好模拟“互斥锁”。它比手写 TaskCompletionSource + 队列更可靠,也避免了 Monitor 无法跨 await 使用的问题。
关键点:
- 必须用
new SemaphoreSlim(1, 1)初始化,确保仅允许一个持有者 - 务必在
finally中调用Release(),否则锁永久泄漏 - 不要复用同一个
SemaphoreSlim实例保护多个无关资源,否则造成不必要串行
public class AsyncLock
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public async ValueTask LockAsync()
{
await _semaphore.WaitAsync();
return new Releaser(_semaphore);
}
private struct Releaser : IDisposable
{
private readonly SemaphoreSlim _semaphore;
public Releaser(SemaphoreSlim semaphore) => _semaphore = semaphore;
public void Dispose() => _semaphore.Release();
}
}
典型使用场景:共享资源的异步初始化与缓存更新
比如单例服务中延迟加载某个 HTTP 客户端配置、或刷新本地缓存时防止多个并发请求重复触发刷新逻辑。
常见误用:
- 在循环里反复
await lock.LockAsync()而没提取出临界区最小粒度,拖慢整体吞吐 - 把整个 HTTP 调用包进锁里,本应只锁“判断是否需刷新 + 标记进行中”这两步
- 忘记
using或未正确处理异常路径,导致Dispose没被调用
private readonly AsyncLock _refreshLock = new AsyncLock(); private DateTimeOffset _lastRefresh = DateTimeOffset.MinValue; public async TaskGetCachedValueAsync() { if (_lastRefresh.AddMinutes(5) < DateTimeOffset.Now) { using (await _refreshLock.LockAsync()) { // 再次检查:防止其他协程已刷新 if (_lastRefresh.AddMinutes(5) < DateTimeOffset.Now) { _cachedValue = await FetchFromApiAsync(); // 真正耗时操作 _lastRefresh = DateTimeOffset.Now; } } } return _cachedValue; }
性能与替代方案提醒
SemaphoreSlim.WaitAsync() 在无竞争时开销极小,但高并发下排队任务会堆积在内部队列,不如同步锁轻量。若临界区纯 CPU 密集且不 await,坚持用 lock 更高效。
更轻量的替代选择(需 .NET 6+):
-
AsyncReaderWriterLock(第三方库如Microsoft.Extensions.Caching.Memory不提供,但Nito.AsyncEx有)适合读多写少 - 对简单标志位控制,可用
Interlocked.CompareExchange+ 循环重试,避免锁开销 - 真正需要协调多个异步操作完成顺序时,考虑
TaskCompletionSource或Channel,而非强行套用AsyncLock
最容易被忽略的一点:AsyncLock 不解决分布式场景问题——它只在单进程内有效。跨服务或跨机器的并发控制,得靠 Redis 锁、数据库行锁或专门的协调服务。










