SemaphoreSlim 保姆级教程
目录
什么是 SemaphoreSlim
SemaphoreSlim 是 .NET 中用于控制同时访问资源或代码段的线程数量的轻量级同步原语。
简单理解:就像一个门禁系统,允许同时最多 N 个人进入,超过的人需要排队等待。
csharp
// 创建一个最多允许 1 个线程同时进入的信号量
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
Semaphore vs SemaphoreSlim
| 特性 | Semaphore | SemaphoreSlim |
|---|---|---|
| 跨进程 | ✅ 支持 | ❌ 不支持 |
| 性能 | 较慢 | 快速(快约10倍) |
| 异步支持 | ❌ 不支持 | ✅ 支持 async/await |
| 推荐场景 | 跨进程同步 | 进程内同步 |
为什么需要它
问题场景
csharp
// ❌ 没有同步保护 - 会出现数据竞争
private int _counter = 0;
async Task BadExample()
{
// 1000 个任务同时修改同一个变量
var tasks = Enumerable.Range(0, 1000).Select(async _ =>
{
_counter++; // 不是原子操作!
await Task.Delay(1);
});
await Task.WhenAll(tasks);
Console.WriteLine(_counter); // 不一定等于 1000!
}
解决方案
csharp
// ✅ 使用 SemaphoreSlim 保护
private int _counter = 0;
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
async Task GoodExample()
{
var tasks = Enumerable.Range(0, 1000).Select(async _ =>
{
await _semaphore.WaitAsync();
try
{
_counter++; // 现在安全了
await Task.Delay(1);
}
finally
{
_semaphore.Release();
}
});
await Task.WhenAll(tasks);
Console.WriteLine(_counter); // 总是 1000
}
核心概念
1. 初始化参数
csharp
// 参数1: initialCount - 初始可用数量
// 参数2: maxCount - 最大可用数量
private SemaphoreSlim _semaphore = new SemaphoreSlim(initialCount: 1, maxCount: 5);
2. 核心方法
csharp
// 同步等待(阻塞线程)
_semaphore.Wait();
_semaphore.Wait(TimeSpan.FromSeconds(1)); // 超时等待
_semaphore.Wait(CancellationToken.None); // 支持取消
// 异步等待(推荐,不阻塞线程)
await _semaphore.WaitAsync();
await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
await _semaphore.WaitAsync(CancellationToken.None);
// 释放信号量
_semaphore.Release(); // 释放一次
_semaphore.Release(3); // 释放多次
// 查看当前状态
int available = _semaphore.CurrentCount; // 当前可用数量
bool canEnter = _semaphore.Wait(0); // 尝试立即进入(不等待)
3. 状态图
初始: Count=3 (最多3人同时进入)
线程A进入 → Wait() → Count=2
线程B进入 → Wait() → Count=1
线程C进入 → Wait() → Count=0
线程D进入 → Wait() → 阻塞等待
线程A退出 → Release() → Count=1 → 唤醒线程D
线程D进入 → Count=0
基本用法
1. 基础同步模式
csharp
public class BasicUsage
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
// 同步版本(不推荐,会阻塞线程)
public void SyncMethod()
{
_semaphore.Wait();
try
{
// 临界区代码 - 同一时间只有一个线程能执行这里
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 正在执行");
Thread.Sleep(1000);
}
finally
{
_semaphore.Release();
}
}
// 异步版本(推荐)
public async Task AsyncMethodAsync()
{
await _semaphore.WaitAsync();
try
{
// 临界区代码 - 异步等待,不阻塞线程
Console.WriteLine($"任务开始");
await Task.Delay(1000);
Console.WriteLine($"任务结束");
}
finally
{
_semaphore.Release();
}
}
}
2. 带超时的等待
csharp
public class TimeoutExample
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public async Task TryExecuteWithTimeoutAsync()
{
// 最多等待 2 秒
if (await _semaphore.WaitAsync(TimeSpan.FromSeconds(2)))
{
try
{
await ExecuteCriticalOperationAsync();
}
finally
{
_semaphore.Release();
}
}
else
{
Console.WriteLine("获取锁超时,执行备用逻辑");
await ExecuteFallbackLogicAsync();
}
}
private async Task ExecuteCriticalOperationAsync()
{
await Task.Delay(1000);
Console.WriteLine("关键操作执行完成");
}
private async Task ExecuteFallbackLogicAsync()
{
await Task.Delay(500);
Console.WriteLine("备用逻辑执行完成");
}
}
3. 支持取消操作
csharp
public class CancellationExample
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private CancellationTokenSource _cts;
public async Task ExecuteWithCancellationAsync()
{
using var cts = new CancellationTokenSource();
// 5秒后自动取消
cts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
await _semaphore.WaitAsync(cts.Token);
try
{
// 长时间运行的操作
await LongRunningOperationAsync(cts.Token);
}
finally
{
_semaphore.Release();
}
}
catch (OperationCanceledException)
{
Console.WriteLine("操作已被取消");
}
}
private async Task LongRunningOperationAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(1000, token);
Console.WriteLine($"进度: {i * 10}%");
}
}
}
4. 允许多个并发
csharp
public class MultiConcurrencyExample
{
// 最多允许 3 个线程同时访问
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3, 3);
public async Task ProcessItemsAsync(List<string> items)
{
var tasks = items.Select(async item =>
{
await _semaphore.WaitAsync();
try
{
await ProcessItemAsync(item);
}
finally
{
_semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
private async Task ProcessItemAsync(string item)
{
Console.WriteLine($"处理 {item} - 线程 {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(1000); // 模拟处理
Console.WriteLine($"完成 {item}");
}
}
实际应用场景
场景1: API 限流
csharp
public class ApiRateLimiter
{
// 每秒最多 10 个请求
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(10, 10);
private readonly TimeSpan _resetInterval = TimeSpan.FromSeconds(1);
private DateTime _lastResetTime = DateTime.UtcNow;
public async Task<T> CallApiAsync<T>(Func<Task<T>> apiCall)
{
// 每秒重置一次
var now = DateTime.UtcNow;
if (now - _lastResetTime >= _resetInterval)
{
var currentCount = _semaphore.CurrentCount;
var releaseCount = 10 - currentCount;
if (releaseCount > 0)
{
_semaphore.Release(releaseCount);
}
_lastResetTime = now;
}
await _semaphore.WaitAsync();
try
{
return await apiCall();
}
finally
{
_semaphore.Release();
}
}
}
// 使用示例
var rateLimiter = new ApiRateLimiter();
var tasks = Enumerable.Range(1, 100).Select(async i =>
{
var result = await rateLimiter.CallApiAsync(async () =>
{
await Task.Delay(100);
return $"请求 {i} 完成";
});
Console.WriteLine(result);
});
await Task.WhenAll(tasks);
场景2: 生产者-消费者队列
csharp
public class ProducerConsumerQueue<T>
{
private readonly SemaphoreSlim _itemSemaphore;
private readonly SemaphoreSlim _consumerSemaphore = new SemaphoreSlim(1, 1);
private readonly Queue<T> _queue = new Queue<T>();
private readonly int _maxQueueSize;
public ProducerConsumerQueue(int maxConcurrentConsumers, int maxQueueSize = 100)
{
_itemSemaphore = new SemaphoreSlim(0, maxQueueSize);
_maxQueueSize = maxQueueSize;
// 启动消费者
for (int i = 0; i < maxConcurrentConsumers; i++)
{
_ = ConsumeAsync();
}
}
public async Task ProduceAsync(T item)
{
await _consumerSemaphore.WaitAsync();
try
{
if (_queue.Count >= _maxQueueSize)
throw new InvalidOperationException("队列已满");
_queue.Enqueue(item);
_itemSemaphore.Release();
}
finally
{
_consumerSemaphore.Release();
}
}
private async Task ConsumeAsync()
{
while (true)
{
await _itemSemaphore.WaitAsync();
T item;
await _consumerSemaphore.WaitAsync();
try
{
item = _queue.Dequeue();
}
finally
{
_consumerSemaphore.Release();
}
await ProcessItemAsync(item);
}
}
private async Task ProcessItemAsync(T item)
{
Console.WriteLine($"处理: {item}");
await Task.Delay(500);
}
}
场景3: 数据库连接池
csharp
public class DatabaseConnectionPool
{
private readonly SemaphoreSlim _semaphore;
private readonly Queue<DbConnection> _availableConnections = new Queue<DbConnection>();
private readonly object _lock = new object();
public DatabaseConnectionPool(int maxConnections)
{
_semaphore = new SemaphoreSlim(maxConnections, maxConnections);
// 初始化连接池
for (int i = 0; i < maxConnections; i++)
{
_availableConnections.Enqueue(CreateConnection());
}
}
public async Task<DbConnection> GetConnectionAsync()
{
await _semaphore.WaitAsync();
lock (_lock)
{
return _availableConnections.Dequeue();
}
}
public void ReturnConnection(DbConnection connection)
{
lock (_lock)
{
_availableConnections.Enqueue(connection);
}
_semaphore.Release();
}
private DbConnection CreateConnection()
{
// 创建实际的数据库连接
return new SqlConnection("connectionString");
}
}
// 使用示例
public class DataService
{
private readonly DatabaseConnectionPool _pool;
public DataService(DatabaseConnectionPool pool)
{
_pool = pool;
}
public async Task<User> GetUserAsync(int id)
{
var connection = await _pool.GetConnectionAsync();
try
{
// 使用连接查询数据
await Task.Delay(100); // 模拟数据库查询
return new User { Id = id, Name = $"User {id}" };
}
finally
{
_pool.ReturnConnection(connection);
}
}
}
场景4: 文件写入协调
csharp
public class FileLogger
{
private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1);
private readonly string _logFilePath;
public FileLogger(string logFilePath)
{
_logFilePath = logFilePath;
}
public async Task LogAsync(string message)
{
await _writeSemaphore.WaitAsync();
try
{
await File.AppendAllTextAsync(_logFilePath,
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}{Environment.NewLine}");
}
finally
{
_writeSemaphore.Release();
}
}
public async Task<List<string>> ReadLogsAsync(int lineCount = 100)
{
await _writeSemaphore.WaitAsync();
try
{
var lines = await File.ReadAllLinesAsync(_logFilePath);
return lines.TakeLast(lineCount).ToList();
}
finally
{
_writeSemaphore.Release();
}
}
}
场景5: UI 线程安全操作(WPF)
csharp
public class SafeUiUpdater
{
private readonly SemaphoreSlim _uiSemaphore = new SemaphoreSlim(1, 1);
private readonly Dispatcher _dispatcher;
public SafeUiUpdater(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public async Task UpdateUIAsync(Action uiAction)
{
await _uiSemaphore.WaitAsync();
try
{
await _dispatcher.InvokeAsync(uiAction);
}
finally
{
_uiSemaphore.Release();
}
}
public async Task<T> GetUIValueAsync<T>(Func<T> getValueFunc)
{
await _uiSemaphore.WaitAsync();
try
{
return await _dispatcher.InvokeAsync(getValueFunc);
}
finally
{
_uiSemaphore.Release();
}
}
}
// WPF ViewModel 中使用
public class MainViewModel : INotifyPropertyChanged
{
private readonly SafeUiUpdater _uiUpdater;
private string _status;
public MainViewModel()
{
_uiUpdater = new SafeUiUpdater(Application.Current.Dispatcher);
}
public string Status
{
get => _status;
set => _uiUpdater.UpdateUIAsync(() =>
{
_status = value;
OnPropertyChanged();
});
}
public async Task ProcessDataAsync()
{
await _uiUpdater.UpdateUIAsync(() => Status = "开始处理...");
await Task.Delay(1000);
await _uiUpdater.UpdateUIAsync(() => Status = "处理完成!");
}
}
高级特性
1. 使用 WaitAsync 避免死锁
csharp
// ❌ 错误的做法 - 可能导致死锁
public async Task DeadlockRisk()
{
_semaphore.Wait(); // 同步等待,阻塞线程
try
{
await SomeAsyncOperation(); // 如果这个操作需要同一线程,就会死锁
}
finally
{
_semaphore.Release();
}
}
// ✅ 正确的做法 - 使用异步等待
public async Task NoDeadlock()
{
await _semaphore.WaitAsync(); // 异步等待,不阻塞线程
try
{
await SomeAsyncOperation(); // 安全
}
finally
{
_semaphore.Release();
}
}
2. 链式等待
csharp
public class ChainedSemaphoreExample
{
private readonly SemaphoreSlim _semaphore1 = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim _semaphore2 = new SemaphoreSlim(1, 1);
public async Task ProcessAsync()
{
// 按照固定顺序获取锁,避免死锁
await _semaphore1.WaitAsync();
try
{
await _semaphore2.WaitAsync();
try
{
await CriticalOperationAsync();
}
finally
{
_semaphore2.Release();
}
}
finally
{
_semaphore1.Release();
}
}
// 使用 using 模式(需要自定义释放器)
public async Task UsingPatternAsync()
{
await using (await _semaphore1.WaitAsync().ConfigureAwait(false))
{
// 自动释放
await CriticalOperationAsync();
}
}
}
// 扩展方法实现自动释放
public static class SemaphoreSlimExtensions
{
public static async Task<SemaphoreReleaser> WaitAsync(this SemaphoreSlim semaphore)
{
await semaphore.WaitAsync();
return new SemaphoreReleaser(semaphore);
}
}
public struct SemaphoreReleaser : IDisposable, IAsyncDisposable
{
private readonly SemaphoreSlim _semaphore;
public SemaphoreReleaser(SemaphoreSlim semaphore)
{
_semaphore = semaphore;
}
public void Dispose()
{
_semaphore?.Release();
}
public ValueTask DisposeAsync()
{
Dispose();
return default;
}
}
3. 动态调整并发数
csharp
public class AdaptiveConcurrencyLimiter
{
private SemaphoreSlim _semaphore;
private readonly object _lock = new object();
public AdaptiveConcurrencyLimiter(int initialConcurrency)
{
_semaphore = new SemaphoreSlim(initialConcurrency, initialConcurrency);
}
public async Task UpdateConcurrencyAsync(int newMaxConcurrency)
{
lock (_lock)
{
var oldSemaphore = _semaphore;
var newSemaphore = new SemaphoreSlim(newMaxConcurrency, newMaxConcurrency);
// 转移等待中的任务(简化版,实际需要更复杂的逻辑)
_semaphore = newSemaphore;
oldSemaphore.Dispose();
}
}
public async Task ExecuteAsync(Func<Task> action)
{
await _semaphore.WaitAsync();
try
{
await action();
}
finally
{
_semaphore.Release();
}
}
}
常见陷阱与最佳实践
陷阱1: 忘记释放
csharp
// ❌ 错误 - 忘记释放
await _semaphore.WaitAsync();
await DoWork(); // 如果这里抛出异常,信号量永远不会释放
// ✅ 正确 - 使用 try/finally
await _semaphore.WaitAsync();
try
{
await DoWork();
}
finally
{
_semaphore.Release();
}
陷阱2: 释放次数超过获取次数
csharp
// ❌ 错误 - 释放过多
_semaphore.Wait();
_semaphore.Release();
_semaphore.Release(); // 异常!超过初始计数
// ✅ 正确 - 一一对应
_semaphore.Wait();
try
{
// work
}
finally
{
_semaphore.Release(); // 只释放一次
}
陷阱3: 在异步代码中使用 Wait()
csharp
// ❌ 错误 - 可能导致死锁
async Task Bad()
{
_semaphore.Wait(); // 阻塞线程
await Task.Delay(1000); // 如果这需要原线程,死锁
_semaphore.Release();
}
// ✅ 正确
async Task Good()
{
await _semaphore.WaitAsync();
await Task.Delay(1000);
_semaphore.Release();
}
最佳实践清单
csharp
public class BestPractices
{
// ✅ 1. 使用 readonly 确保不会意外替换
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
// ✅ 2. 实现 IDisposable
public class ResourceHandler : IDisposable
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private bool _disposed;
public async Task ProcessAsync()
{
ObjectDisposedException.ThrowIf(_disposed, this);
await _semaphore.WaitAsync();
try
{
await Task.Delay(100);
}
finally
{
_semaphore.Release();
}
}
public void Dispose()
{
if (!_disposed)
{
_semaphore.Dispose();
_disposed = true;
}
}
}
// ✅ 3. 使用 ConfigureAwait(false) 避免上下文切换
public async Task LibraryMethodAsync()
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
await Task.Delay(100).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
// ✅ 4. 指定合理的超时时间
public async Task<bool> TryExecuteWithTimeoutAsync(TimeSpan timeout)
{
if (await _semaphore.WaitAsync(timeout))
{
try
{
await Task.Delay(100);
return true;
}
finally
{
_semaphore.Release();
}
}
return false;
}
}
完整示例项目
多线程下载管理器
csharp
public class DownloadManager
{
private readonly SemaphoreSlim _concurrencyLimiter;
private readonly ConcurrentDictionary<string, DownloadTask> _activeDownloads;
private readonly IProgress<DownloadProgress> _progress;
public DownloadManager(int maxConcurrentDownloads, IProgress<DownloadProgress> progress = null)
{
_concurrencyLimiter = new SemaphoreSlim(maxConcurrentDownloads, maxConcurrentDownloads);
_activeDownloads = new ConcurrentDictionary<string, DownloadTask>();
_progress = progress;
}
public async Task<string> DownloadFileAsync(string url, string savePath)
{
var taskId = Guid.NewGuid().ToString();
var downloadTask = new DownloadTask { Url = url, SavePath = savePath, Id = taskId };
_activeDownloads.TryAdd(taskId, downloadTask);
// 等待获得下载许可
await _concurrencyLimiter.WaitAsync();
try
{
downloadTask.Status = DownloadStatus.Downloading;
_progress?.Report(new DownloadProgress(taskId, 0, DownloadStatus.Downloading));
// 模拟下载
for (int i = 0; i <= 100; i += 10)
{
await Task.Delay(500);
downloadTask.Progress = i;
_progress?.Report(new DownloadProgress(taskId, i, DownloadStatus.Downloading));
// 支持取消
if (downloadTask.CancellationToken.IsCancellationRequested)
{
downloadTask.Status = DownloadStatus.Cancelled;
throw new OperationCanceledException();
}
}
downloadTask.Status = DownloadStatus.Completed;
_progress?.Report(new DownloadProgress(taskId, 100, DownloadStatus.Completed));
return savePath;
}
catch (Exception ex)
{
downloadTask.Status = DownloadStatus.Failed;
downloadTask.Error = ex.Message;
_progress?.Report(new DownloadProgress(taskId, downloadTask.Progress, DownloadStatus.Failed));
throw;
}
finally
{
_activeDownloads.TryRemove(taskId, out _);
_concurrencyLimiter.Release();
}
}
public async Task CancelDownloadAsync(string taskId)
{
if (_activeDownloads.TryGetValue(taskId, out var task))
{
task.Cancel();
}
}
public async Task<IEnumerable<DownloadTask>> GetActiveDownloadsAsync()
{
return _activeDownloads.Values.ToList();
}
}
public class DownloadTask
{
public string Id { get; set; }
public string Url { get; set; }
public string SavePath { get; set; }
public int Progress { get; set; }
public DownloadStatus Status { get; set; }
public string Error { get; set; }
private CancellationTokenSource _cts = new CancellationTokenSource();
public CancellationToken CancellationToken => _cts.Token;
public void Cancel() => _cts.Cancel();
}
public enum DownloadStatus
{
Pending,
Downloading,
Completed,
Failed,
Cancelled
}
public record DownloadProgress(string TaskId, int Progress, DownloadStatus Status);
// 使用示例
class Program
{
static async Task Main()
{
var progress = new Progress<DownloadProgress>(p =>
{
Console.WriteLine($"[{p.TaskId.Substring(0, 8)}] {p.Status}: {p.Progress}%");
});
var manager = new DownloadManager(maxConcurrentDownloads: 3, progress);
var urls = Enumerable.Range(1, 10).Select(i => $"https://example.com/file{i}.zip").ToList();
var tasks = urls.Select((url, index) =>
manager.DownloadFileAsync(url, $"file{index}.zip"));
await Task.WhenAll(tasks);
Console.WriteLine("所有下载完成!");
}
}
总结
核心要点
- SemaphoreSlim 用于控制并发数量,特别适合异步场景
- 始终使用 try/finally 确保释放信号量
- 优先使用 WaitAsync() 而不是 Wait(),避免死锁
- 合理设置超时时间,避免无限等待
- 实现 IDisposable 释放资源
选择指南
| 需求 | 推荐方案 |
|---|---|
| 保护单个资源(一次一个线程) | new SemaphoreSlim(1, 1) |
| 限制并发数量(如 API 限流) | new SemaphoreSlim(5, 5) |
| 生产者-消费者模式 | 结合 Queue 使用 |
| 跨进程同步 | 使用 Semaphore(不是 Slim) |
| 高性能场景 | SemaphoreSlim(比 lock 更快) |
希望这个教程能帮助你完全掌握 SemaphoreSlim!记住:获取后务必释放,异步用 WaitAsync,同步用 try/finally。