Потрібно зрозуміти використання 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();?


3
Одне, на що слід звернути увагу, це те, що вам дійсно слід обернути те, що "DoPollingThenWorkAsync ();" у "спробуйте {DoPollingThenWorkAsync ();} нарешті {ss.Release ();}", інакше винятки назавжди зморять цей семафор.
Остін Сальгат

Мені здається трохи дивним, що ми набуваємо та випускаємо семафор відповідно / поза завданням. Чи змінить значення "await ss.WaitAsync ()" всередині завдання?
Шейн Лу

Відповіді:


73

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

Це правильно; використання семафору гарантує, що одночасно не буде більше 10 робітників, які виконують цю роботу.

Виклик WaitAsyncсемафору створює завдання, яке буде виконане, коли цьому потоку буде надано "доступ" до цього маркера. await-ing цього завдання дозволяє програмі продовжувати виконання, коли це "дозволено" робити. Наявність асинхронної версії, а не виклик Wait, важливо як для того, щоб метод залишався асинхронним, а не синхронним, а також мав на увазі той факт, що asyncметод може виконувати код через кілька потоків, завдяки зворотним викликам, і так далі проблема спорідненості природних ниток із семафорами.

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


спасибі, але, будь ласка, скажіть мені, що станеться, коли ми вкажемо, що немає потоку для запуску, скажімо 10. коли один із 10 потоків закінчує, тоді знову цей потік переходить на закінчення іншого завдання або повертається до пулу? це не дуже зрозуміло для .... тому, будь ласка, поясніть, що сталося за кадром.
Mou

@Mou Що з цього незрозуміло? Код чекає, поки не буде виконано менше 10 завдань; коли є, це додає ще одне. Коли завдання закінчується, це означає, що воно виконане. Це воно.
Серві

яка перевага вказування номера потоку для запуску. якщо занадто багато ниток може заважати продуктивності? якщо так, то чому перешкоджати ... якщо я запускаю 50 потоків замість 10 потоків, то чому продуктивність матиме значення ... може, поясніть, будь ласка. подяка
Томас

4
@Thomas Якщо у вас занадто багато одночасних потоків, тоді потоки витрачають більше часу на перемикання контексту, ніж витрачають на продуктивну роботу. Пропускна здатність зменшується, оскільки потоки зростають, оскільки ви витрачаєте все більше часу на управління потоками, а не виконуєте роботу, принаймні, коли кількість потоків значно перевищує кількість ядер на машині.
Серві

3
@Servy Це частина роботи планувальника завдань. Завдання! = Нитки. В Thread.Sleepоригінальному коді буде зруйновано планувальник завдань. Якщо ви не асинхронізуєтесь із ядром, ви не асинхронізуєтесь.
Джозеф Леннокс,

54

У дитячому садку за рогом вони використовують SemaphoreSlim, щоб контролювати, скільки дітей може грати в кімнаті для фізичних вправ.

Вони намалювали на підлозі, зовні кімнати, 5 пар слідів ніг.

Коли діти приїжджають, вони залишають взуття на вільних парах слідів і входять до кімнати.

Закінчивши гру, вони виходять, збирають взуття і "звільняють" слот для іншої дитини.

Якщо дитина приїжджає, а слідів не залишилось, вони їдуть грати в інше місце або просто затримуються на деякий час і перевіряють час від часу (тобто немає пріоритетів FIFO).

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

У нього також є ті самі "підводні камені" SemaphoreSlim ...

Якщо дитина закінчує гру і виходить із кімнати, не збираючи взуття (не викликає "звільнення"), тоді слот залишається заблокованим, хоча теоретично там є порожній слот. Хлопця, як правило, відмовляють.

Іноді один-два підлих малюка ховають взуття в іншому місці і заходять у кімнату, навіть якщо всі сліди вже зроблені (тобто SemaphoreSlim не "насправді" контролює, скільки дітей у кімнаті).

Зазвичай це не закінчується добре, оскільки переповненість кімнати, як правило, закінчується плачем дітей, а вчитель повністю закриває кімнату.


3
Такі відповіді є моїми улюбленими.
Мій стек переповнюється

OMG це інформативно та смішно, як чорт!
Zonus

7

Хоча я приймаю, що це питання справді стосується сценарію блокування зворотного відліку, я вважав, що варто поділитися цим посиланням, яке я знайшов для тих, хто бажає використовувати SemaphoreSlim як простий асинхронний замок. Це дозволяє використовувати оператор using, який може зробити кодування більш акуратним та безпечним.

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

Я робив обмін _isDisposed=trueі _semaphore.Release()навколо в його Dispose, хоча на випадок, якщо його якось викликали кілька разів.

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

Що стосується питань якості коду, то краще поставити Release в кінець спроби-нарешті, щоб він завжди вийшов.


6
Недоцільно публікувати відповіді лише на посилання, оскільки посилання, як правило, відмирають з часом, що робить відповідь нікчемною. Якщо ви можете, найкраще підсумуйте ключові моменти або блок ключових кодів у своїй відповіді.
Джон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.