В чем разница между Semaphore и SemaphoreSlim в C#?
Что проверяют
Знаете ли вы, что в асинхронном коде подходит только один из двух, и понимаете ли цену системных примитивов.
Как отвечать
Semaphore это обёртка над семафором операционной системы. Он тяжелее, зато умеет то, чего не умеет облегчённый вариант: работать между процессами, если ему дать имя.
SemaphoreSlim живёт полностью в управляемом коде. Он легче, быстрее на коротких ожиданиях (сначала крутится в цикле ожидания и только потом уходит в блокировку) и, главное, поддерживает WaitAsync, то есть не занимает поток на время ожидания.
Отсюда правило выбора: всё внутри одного процесса это SemaphoreSlim, синхронизация между процессами это Semaphore с именем.
private readonly SemaphoreSlim _gate = new(3); // не больше трёх одновременных вызовов
await _gate.WaitAsync(cancellationToken);
try { await CallExternalApiAsync(); }
finally { _gate.Release(); }Обязательно проговорите три вещи: освобождение только в finally, иначе исключение навсегда съедает слот; SemaphoreSlim реализует IDisposable, и его нужно освобождать; отмена через CancellationToken поддерживается прямо в WaitAsync.
Полезно добавить, чем семафор отличается от lock: lock пускает одного и привязан к потоку, поэтому в асинхронном коде он неприменим (нельзя ждать внутри lock), а семафор пускает N и не привязан к потоку вовсе.
Где спотыкаются
Используют lock вокруг await и получают либо ошибку компиляции, либо неверную синхронизацию через обходные пути.
Вызывают синхронный Wait() у SemaphoreSlim в асинхронном коде и блокируют поток из пула, ради экономии которого всё и затевалось.
Забывают Release при исключении, и приложение постепенно упирается в исчерпанный семафор.