c# AsyncSemaphore 和 SemaphoreSlim 的区别

5次阅读

asyncsemaphore 不是 .net 原生类型,而是社区基于 semaphoreslim 封装的语法糖;semaphoreslim 本身已原生支持异步等待(waitasync)和取消令牌,直接使用更简可控。

c# AsyncSemaphore 和 SemaphoreSlim 的区别

没有 AsyncSemaphore 这个类型 —— 它是社区封装的“语法糖”,不是 .NET 原生类;而 SemaphoreSlim 是官方提供的、支持异步等待的轻量信号量。

为什么搜不到 AsyncSemaphore 的官方文档?

因为 AsyncSemaphore 并不存在于 System.Threading 或任何 .NET 标准库中。它是开发者(比如在 NuGet 上的 microsoft.visualstudio.Threading 或某些开源项目)基于 SemaphoreSlim 封装的一层薄包装,目的通常是:

  • WaitAsync() + using 模式固化成 await using 友好结构
  • 自动处理 Release()(比如用 IDisposableIAsyncDisposable
  • 隐藏 CancellationToken 传递细节

它不解决新问题,只是让常见用法更安全、不易漏 Release

SemaphoreSlim 本身已原生支持异步:别绕路封装

.NET Framework 4.5+ 和所有 .NET Core / .NET 5+ 都自带 SemaphoreSlim.WaitAsync(),无需额外引入“AsyncSemaphore”类。直接用它就是最简、最可控的方式:

var semaphore = new SemaphoreSlim(initialCount: 3, maxCount: 3); <p>await semaphore.WaitAsync(cancellationToken); try { // 执行受保护的异步操作 await SomeIoOperationAsync(); } finally { semaphore.Release(); }

注意要点:

  • WaitAsync() 支持 CancellationToken,能响应取消请求(Semaphore 不支持)
  • 必须配对 Release(),否则计数泄漏 → 后续所有 WaitAsync() 都会永久挂起
  • 不要在 catch 块外释放 —— try/finally 是底线,using 不适用(它没实现 IAsyncDisposable

什么时候才该考虑封装一个“AsyncSemaphore”?

仅当你的项目反复出现以下模式,且团队多人容易写错时:

  • 每次都要手写 try/finally + Release()
  • 经常忘记传 cancellationToken,导致无法取消等待
  • 需要统一记录等待超时、统计并发峰值等可观测性逻辑

此时可自己写一个极简封装(20 行内),例如:

public sealed class AsyncSemaphore : IAsyncDisposable {     private readonly SemaphoreSlim _semaphore; <pre class='brush:php;toolbar:false;'>public AsyncSemaphore(int initialCount) => _semaphore = new SemaphoreSlim(initialCount);  public async ValueTask<IDisposable> EnterAsync(CancellationToken ct = default) {     await _semaphore.WaitAsync(ct);     return new Releaser(_semaphore); }  private struct Releaser : IDisposable {     private readonly SemaphoreSlim _s;     public Releaser(SemaphoreSlim s) => _s = s;     public void Dispose() => _s.Release(); }  public async ValueTask DisposeAsync() => await Task.CompletedTask;

}

但请记住:这仍是建立在 SemaphoreSlim 之上的便利层,不是替代品。

真正容易被忽略的是:无论用原生 SemaphoreSlim 还是自封的 AsyncSemaphore,都必须确保 Release() 在 *每一次* WaitAsync() 成功后被执行 —— 缺一次,整个信号量就卡死。这不是异常,也不会报错,只会让后续所有线程/任务无声地 hang 住。

text=ZqhQzanResources