Необходимо понимать использование SemaphoreSlim

90

Вот код, который у меня есть, но я не понимаю, что SemaphoreSlimпроисходит.

async Task WorkerMainAsync()
{
    SemaphoreSlim ss = new SemaphoreSlim(10);
    List<Task> trackedTasks = new List<Task>();
    while (DoMore())
    {
        await ss.WaitAsync();
        trackedTasks.Add(Task.Run(() =>
        {
            DoPollingThenWorkAsync();
            ss.Release();
        }));
    }
    await Task.WhenAll(trackedTasks);
}

void DoPollingThenWorkAsync()
{
    var msg = Poll();
    if (msg != null)
    {
        Thread.Sleep(2000); // process the long running CPU-bound job
    }
}

Что ждет ss.WaitAsync();и ss.Release();делает?

Я предполагаю, что если я запускаю 50 потоков за раз, тогда напишу код, например, SemaphoreSlim ss = new SemaphoreSlim(10);тогда он будет вынужден запускать 10 активных потоков за раз.

Когда один из 10 потоков завершается, запускается другой поток. Если я не прав, помогите мне разобраться с образцом ситуации.

Зачем awaitнужен вместе с ss.WaitAsync();? Что ss.WaitAsync();делать?

Mou
источник
3
Следует отметить, что вам действительно следует обернуть это «DoPollingThenWorkAsync ();» в "try {DoPollingThenWorkAsync ();} finally {ss.Release ();}", иначе исключения будут постоянно истощать этот семафор.
Остин Салгат
Мне немного странно, что мы получаем и освобождаем семафор вне / внутри задачи соответственно. Будет ли иметь какое-либо значение перемещение «await ss.WaitAsync ()» внутри задачи?
Шейн Лу

Ответы:

73

я предполагаю, что если я запускаю 50 потоков за раз, тогда код вроде SemaphoreSlim ss = new SemaphoreSlim (10); заставит запускать 10 активных потоков за раз

Это правильно; использование семафора гарантирует, что эту работу будут выполнять не более 10 рабочих одновременно.

Вызов WaitAsyncсемафора создает задачу, которая будет завершена, когда этому потоку будет предоставлен «доступ» к этому токену. await-исполнение этой задачи позволяет программе продолжить выполнение, когда это «разрешено». Наличие асинхронной версии, а не ее вызова Wait, важно как для обеспечения того, чтобы метод оставался асинхронным, а не синхронным, так и для решения того факта, что asyncметод может выполнять код в нескольких потоках из-за обратных вызовов и т. Д. естественное сходство потоков с семафорами может быть проблемой.

Примечание: постфикса DoPollingThenWorkAsyncне должно быть, Asyncпотому что он на самом деле не асинхронный, а синхронный. Просто позвони DoPollingThenWork. Это уменьшит путаницу для читателей.

Сервировка
источник
спасибо, но, пожалуйста, скажите мне, что произойдет, когда мы не укажем ни одного потока для запуска, скажем 10. когда один из 10 потоков завершится, затем снова этот поток перейдет к завершению других заданий или вернется в пул? это не очень понятно .... так что объясните, пожалуйста, что происходит за сценой.
Mou
@Mou Что в этом непонятного? Код ожидает, пока в данный момент не будет запущено менее 10 задач; когда есть, добавляет еще один. Когда задача завершается, это означает, что она завершена. Вот и все.
Servy
в чем преимущество указания отсутствия потока для запуска. если слишком много потоков может снизить производительность? если да, то зачем мешать ... если я запускаю 50 потоков вместо 10, тогда почему производительность будет иметь значение ... не могли бы вы объяснить. спасибо
Thomas
4
@Thomas. Если у вас слишком много параллельных потоков, они тратят больше времени на переключение контекста, чем на продуктивную работу. Пропускная способность снижается по мере увеличения количества потоков, поскольку вы тратите все больше и больше времени на управление потоками вместо выполнения работы, по крайней мере, если количество потоков значительно превышает количество ядер на машине.
Servy
3
@Servy Это часть работы планировщика задач. Задачи! = Темы. Символ Thread.Sleepв исходном коде разрушит планировщик задач. Если вы не асинхронны с ядром, вы не асинхронны.
Джозеф Леннокс
54

В детском саду за углом они используют SemaphoreSlim, чтобы контролировать, сколько детей могут играть в спортивной комнате.

Они нарисовали на полу за пределами комнаты 5 пар следов ног.

Когда дети приходят, они оставляют обувь на свободных следах и входят в комнату.

Закончив игру, они выходят, собирают обувь и «освобождают» место для другого ребенка.

Если приходит ребенок и не остается следов, он идет поиграть в другое место или просто остается на некоторое время и время от времени проверяет (т.е. без приоритетов FIFO).

Когда учитель рядом, она «выпускает» дополнительный ряд из 5 следов на другой стороне коридора, так что еще 5 детей могут играть в комнате одновременно.

Там же есть те же "подводные камни" SemaphoreSlim ...

Если ребенок заканчивает игру и выходит из комнаты, не собрав обувь (не запускает «отпускание»), то слот остается заблокированным, даже если теоретически есть пустой слот. Однако обычно ребенка ругают.

Иногда один или два подлых ребенка прячут свою обувь в другом месте и входят в комнату, даже если все следы уже заняты (например, SemaphoreSlim «на самом деле» не контролирует, сколько детей находится в комнате).

Обычно это не заканчивается хорошо, так как переполненность комнаты, как правило, заканчивается тем, что дети плачут, а учитель полностью закрывает комнату.

денди
источник
3
Я люблю такие ответы.
Мой стек переполняется
Боже мой, это чертовски информативно и забавно!
Зонус
7

Хотя я согласен, что этот вопрос действительно относится к сценарию блокировки обратного отсчета, я подумал, что стоит поделиться этой ссылкой, которую я обнаружил, для тех, кто хочет использовать SemaphoreSlim в качестве простой асинхронной блокировки. Это позволяет использовать оператор using, который может сделать кодирование более аккуратным и безопасным.

http://www.tomdupont.net/2016/03/how-to-release-semaphore-with-using.html

Я сделал своп _isDisposed=trueи _semaphore.Release()вокруг в Dispose , хотя в случае , если она каким - то образом вызывается несколько раз.

Также важно отметить, что SemaphoreSlim не является реентерабельной блокировкой, то есть, если один и тот же поток вызывает WaitAsync несколько раз, счетчик семафора каждый раз уменьшается. Короче говоря, SemaphoreSlim не поддерживает потоки.

Что касается вопросов о качестве кода, лучше поместить выпуск в конец попытки, чтобы гарантировать, что он всегда будет выпускаться.

Эндрю Пэйт
источник
6
Не рекомендуется публиковать ответы только по ссылкам, поскольку ссылки со временем умирают, что делает ответ бесполезным. Если вы можете, лучше всего кратко изложить ключевые моменты или ключевой блок кода в своем ответе.
Джон