如何实现类似WaitHandle.WaitAny的多SemaphoreSlim异步等待?
异步等待多个信号量的高效实现方案
首先替换原有的Semaphore为SemaphoreSlim(它原生支持异步等待,且完全保留限流能力),再通过Task.WhenAny结合任务包装逻辑,实现类似WaitHandle.WaitAny的异步效果,全程无线程池线程阻塞。
核心实现代码
SemaphoreSlim sem1 = new SemaphoreSlim(1, 1); SemaphoreSlim sem2 = new SemaphoreSlim(1, 1); // 给每个信号量的异步等待任务绑定对应索引 var waitTasks = new List<Task<int>> { sem1.WaitAsync().ContinueWith(_ => 0), sem2.WaitAsync().ContinueWith(_ => 1) }; // 等待第一个可用的信号量,获取其索引 int index = await Task.WhenAny(waitTasks).Unwrap(); if (index == 0) { try { // do some operation } finally { sem1.Release(); // 必须释放信号量,避免泄漏 } } else if (index == 1) { try { // do some other operation } finally { sem2.Release(); } }
方案细节说明
- 无阻塞特性:所有等待逻辑均为异步非阻塞,不会占用线程池线程闲置等待信号量,符合异步编程的资源高效利用原则。
- 限流能力保留:
SemaphoreSlim和原Semaphore一样支持设置初始许可数与最大许可数,完全满足限流需求。 - 索引精准对应:通过
ContinueWith为每个WaitAsync任务绑定信号量的索引,确保任务完成后能准确识别是哪个信号量可用。
通用封装(复用场景)
如果需要在多处使用该逻辑,可以封装成通用方法,支持任意数量的SemaphoreSlim:
public static async Task<int> WaitAnyAsync(params SemaphoreSlim[] semaphores) { var waitTasks = semaphores.Select((sem, idx) => sem.WaitAsync().ContinueWith(_ => idx)).ToList(); return await Task.WhenAny(waitTasks).Unwrap(); }
调用示例:
int index = await WaitAnyAsync(sem1, sem2); // 后续操作逻辑同上
关键注意事项
- 必须在操作完成后调用
Release()释放信号量,建议放在finally块中,防止异常导致信号量泄漏。 - 如需支持超时,可给
WaitAsync传入超时参数(如sem1.WaitAsync(TimeSpan.FromSeconds(3))),此时需扩展包装逻辑,返回包含索引和是否成功获取许可的结果,避免超时后错误执行操作。
内容的提问来源于stack exchange,提问作者Rishi Kaushik
相关产品推荐
相关产品推荐

