В чем различия между SemaphoreSlim и Semaphore?

Ответ

SemaphoreSlim — это легковесная, управляемая (user-mode) реализация семафора для синхронизации в пределах одного процесса. Semaphore — это обёртка над объектом ядра ОС, которая поддерживает межпроцессную синхронизацию.

Сравнительная таблица: Характеристика SemaphoreSlim Semaphore
Область видимости Внутрипроцессная Межпроцессная (можно именовать)
Производительность Высокая (избегает переключения в режим ядра) Низкая (дорогой системный вызов)
Асинхронная поддержка Есть (WaitAsync()) Нет (только синхронный WaitOne())
Инициализация Только в коде Может быть создан по имени (для общего доступа)
Лимит счетчика int.MaxValue Определяется ОС
Рекомендуемый сценарий Синхронизация потоков внутри приложения Координация работы нескольких процессов или с устаревшим COM-кодом

Пример SemaphoreSlim (ограничение параллелизма):

// Допускаем не более 5 одновременных запросов к внешнему API
private static readonly SemaphoreSlim _throttler = new SemaphoreSlim(5);

public async Task<Data> GetDataAsync()
{
    await _throttler.WaitAsync(); // Асинхронное ожидание
    try
    {
        // Критическая секция с ограниченным параллелизмом
        return await _httpClient.GetFromJsonAsync<Data>(...);
    }
    finally
    {
        _throttler.Release();
    }
}

Пример Semaphore (межпроцессная блокировка):

// Создаём или открываем именованный семафор, доступный всем процессам на машине
using var semaphore = new Semaphore(initialCount: 1, maximumCount: 1, name: "GlobalMyAppSemaphore");
semaphore.WaitOne(); // Блокируем доступ для других процессов
try
{
    // Работа с общим ресурсом (файлом, портом и т.д.)
}
finally
{
    semaphore.Release();
}

Вывод: Для большинства сценариев синхронизации потоков внутри современного .NET-приложения используйте SemaphoreSlim. К Semaphore прибегайте только при необходимости координации между независимыми процессами.

Ответ 18+ 🔞

Слушай, а вот эта вся хуйня с семафорами — это как два брата-близнеца, но один на спортплощадке гантели кидает, а второй в офисном кресле пукает в тишине. Короче, разбираемся.

Есть у нас SemaphoreSlim. Это такой шустрый, легковесный пацан, который работает только внутри твоего процесса. Он не лезет в ядро операционки, поэтому не тормозит как чёрт. А есть его старший братан Semaphore — это уже серьёзный дядька, обёртка над системным объектом. Он может синхронизировать разные процессы, но за это платит производительностью, потому что каждый раз надо идти в ядро — это долго и муторно.

Краткая памятка, чтобы не ебал мозг: Что сравниваем SemaphoreSlim Semaphore
Где работает Внутри одного процесса Между разными процессами (можно по имени создать)
Скорость Быстро, почти как обычная переменная Медленно, идёт в ядро ОС
Асинхронность Поддерживает! Есть WaitAsync() — красота. Не поддерживает, только синхронный WaitOne(), который может заблокировать поток.
Как создаётся Только в коде, через new. Можно создать по имени, чтобы другие процессы его увидели.
Сколько потоков пускать До int.MaxValue (2+ миллиарда, хватит всем). Зависит от ОС, но обычно тоже дофига.
Когда юзать Почти всегда, когда нужно ограничить параллелизм внутри приложения. Только если нужно синхронизировать несколько независимых .exe-шников или работать с каким-то легаси-кодом.

Вот тебе живой пример с SemaphoreSlim (чтобы не положить API своими запросами):

// Не пускаем больше 5 потоков одновременно к нашему бедному API
private static readonly SemaphoreSlim _throttler = new SemaphoreSlim(5);

public async Task<Data> GetDataAsync()
{
    // Асинхронно ждём своей очереди — поток не блокируется!
    await _throttler.WaitAsync();
    try
    {
        // Тут только 5 потоков максимум будут работать
        return await _httpClient.GetFromJsonAsync<Data>(...);
    }
    finally
    {
        // Обязательно отпускаем, а то все встанут колом
        _throttler.Release();
    }
}

А вот пример с Semaphore (когда два разных приложения лезут в один файл):

// Создаём или открываем семафор с именем. "Global" значит, что он виден всем на машине.
using var semaphore = new Semaphore(initialCount: 1, maximumCount: 1, name: @"GlobalMyAppSemaphore");
// Ждём, пока не станем единственными владельцами
semaphore.WaitOne();
try
{
    // Тут работаем с чем-то общим, например, пишем в конфиг
    File.WriteAllText("shared.txt", "Пишу и никого не пущаю!");
}
finally
{
    // Отпускаем, чтобы другие процессы могли зайти
    semaphore.Release();
}

Итог простой, блядь: если твои потоки дерутся внутри одного приложения — бери SemaphoreSlim и не парься. Он быстрый и умеет в async/await. Старшего брата Semaphore тащи только тогда, когда нужно договориться с другим процессом, например, с соседней службой или старым COM-компонентом. Во всех остальных случаях это просто стрельба из пушки по воробьям.