在现代 C# 开发中,异步编程已成为处理高并发、高性能场景的标准方式。然而,当多个异步任务需要安全地访问共享资源时,传统的 lock 关键字不再适用——因为它会阻塞线程,违背了异步非阻塞的设计初衷。这时,我们就需要使用 C#异步锁(如 AsyncLock)来协调并发。
但你是否思考过:这些异步锁是“公平”的吗?也就是说,先请求锁的任务是否一定会先获得锁?本文将带你深入理解 公平锁 的概念,并教你如何在 C# 中实现具有公平性控制的异步锁。
异步锁是一种专为 async/await 编程模型设计的同步原语。它允许任务在等待锁时不阻塞线程,而是挂起并释放线程资源,待锁可用时再恢复执行。
常见的实现基于 SemaphoreSlim 或自定义队列机制。例如:
public class AsyncLock{ private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Task<Releaser> _releaser; public AsyncLock() { _releaser = Task.FromResult(new Releaser(this)); } public async Task<Releaser> LockAsync() { await _semaphore.WaitAsync(); return await _releaser; } public struct Releaser : IDisposable { private readonly AsyncLock _toRelease; internal Releaser(AsyncLock toRelease) => _toRelease = toRelease; public void Dispose() { if (_toRelease != null) _toRelease._semaphore.Release(); } }} 这个基础版 AsyncLock 能工作,但它不保证公平性!因为 SemaphoreSlim 内部使用的是无序的等待任务集合,后到的任务可能比先到的更早获得锁。
在某些场景下(如任务调度、资源分配、日志写入等),我们希望严格按照请求顺序授予锁,避免“饥饿”问题——即某些任务因总是被插队而长时间得不到执行。
这就是 公平锁 的价值所在:它通过 FIFO(先进先出)队列确保先请求者先获得锁。
要实现公平性,我们需要显式维护一个任务等待队列。以下是基于 Queue<TaskCompletionSource<bool>> 的公平 AsyncLock 实现:
using System.Collections.Concurrent;public class FairAsyncLock{ private readonly object _syncRoot = new(); private readonly Queue<TaskCompletionSource<bool>> _waiters = new(); private bool _isLocked = false; public async Task<Releaser> LockAsync() { TaskCompletionSource<bool> tcs = null; lock (_syncRoot) { if (!_isLocked) { _isLocked = true; return new Releaser(this); } // 锁已被占用,加入等待队列 tcs = new TaskCompletionSource<bool>(); _waiters.Enqueue(tcs); } // 等待被唤醒 await tcs.Task; return new Releaser(this); } private void Release() { lock (_syncRoot) { if (_waiters.Count == 0) { _isLocked = false; return; } // 唤醒队列中的第一个等待者(FIFO) var next = _waiters.Dequeue(); next.SetResult(true); } } public struct Releaser : IDisposable { private readonly FairAsyncLock _lock; public Releaser(FairAsyncLock fairLock) => _lock = fairLock; public void Dispose() => _lock?.Release(); }} 关键点解析:
lock(_syncRoot) 保护内部状态,确保线程安全。Queue<T> 维护等待任务的顺序,严格 FIFO。Dispose() 时,才会唤醒下一个等待者。var fairLock = new FairAsyncLock();async Task Worker(string name){ using (await fairLock.LockAsync()) { Console.WriteLine($"{name} 获得锁"); await Task.Delay(100); // 模拟工作 Console.WriteLine($"{name} 释放锁"); }}// 启动多个并发任务var tasks = Enumerable.Range(1, 5) .Select(i => Worker($"Task{i}")) .ToArray();await Task.WhenAll(tasks);// 输出顺序将严格为:Task1 → Task2 → Task3 → Task4 → Task5 运行上述代码,你会发现输出顺序始终与任务启动顺序一致,这正是 公平锁 的体现。
公平锁虽然保证了顺序,但可能带来轻微的性能开销(需维护队列和额外同步)。在大多数 I/O 密集型场景中,这种开销可忽略不计。
如果你不需要严格顺序,标准 AsyncLock(基于 SemaphoreSlim)通常更高效。但在需要确定性行为或避免饥饿的系统中,公平的异步锁 是不可或缺的工具。
本文详细讲解了 C# 异步锁的公平性问题,并提供了可直接使用的 FairAsyncLock 实现。通过显式队列管理,我们确保了锁请求的 FIFO 顺序,解决了传统异步锁可能存在的不公平问题。
掌握 C#异步锁、公平锁、AsyncLock 和 异步编程 的核心原理,将帮助你在高并发应用中构建更可靠、可预测的同步逻辑。
提示:在生产环境中,也可考虑使用成熟的库如 AsyncEx 中的 AsyncLock,它已内置公平性选项。
本文由主机测评网于2025-12-25发表在主机测评网_免费VPS_免费云服务器_免费独立服务器,如有疑问,请联系我们。
本文链接:https://www.vpshk.cn/20251212531.html