Musisz zrozumieć użycie SemaphoreSlim

90

Oto kod, który mam, ale nie rozumiem, co SemaphoreSlimrobi.

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
    }
}

Co czeka ss.WaitAsync();i co ss.Release();robi?

Wydaje mi się, że jeśli uruchomię 50 wątków na raz, a potem napiszę kod w SemaphoreSlim ss = new SemaphoreSlim(10);ten sposób, będzie zmuszony do uruchomienia 10 aktywnych wątków na raz.

Gdy jeden z 10 wątków zostanie zakończony, rozpocznie się kolejny wątek. Jeśli nie mam racji, pomóż mi zrozumieć przykładową sytuację.

Dlaczego jest awaitpotrzebny wraz z ss.WaitAsync();? Co robi ss.WaitAsync();?

Mou
źródło
3
Należy zauważyć, że naprawdę należy opakować to "DoPollingThenWorkAsync ();" w "try {DoPollingThenWorkAsync ();} w końcu {ss.Release ();}", w przeciwnym razie wyjątki na stałe zagłodzą ten semafor.
Austin Salgat
Czuję się trochę dziwnie, że pozyskujemy i zwalniamy semafor odpowiednio na zewnątrz / wewnątrz zadania. Czy przeniesienie funkcji „await ss.WaitAsync ()” wewnątrz zadania ma znaczenie?
Shane Lu

Odpowiedzi:

73

Wydaje mi się, że jeśli uruchomię 50 wątków naraz, kod taki jak SemaphoreSlim ss = new SemaphoreSlim (10); wymusi uruchomienie 10 aktywnych wątków na raz

To jest poprawne; zastosowanie semafora gwarantuje, że nie będzie więcej niż 10 pracowników wykonujących tę pracę w tym samym czasie.

Wywołanie WaitAsyncsemafora powoduje utworzenie zadania, które zostanie zakończone, gdy wątek uzyska „dostęp” do tego tokenu. await- wykonanie tego zadania pozwala programowi kontynuować wykonywanie, gdy jest to „dozwolone”. Posiadanie wersji asynchronicznej, a nie wywoływanie Wait, jest ważne zarówno dla zapewnienia, że ​​metoda pozostanie asynchroniczna, a nie synchroniczna, a także zajmuje się faktem, że asyncmetoda może wykonywać kod w kilku wątkach ze względu na wywołania zwrotne, itd. problem może stanowić naturalne powinowactwo nici do semaforów.

Na marginesie: DoPollingThenWorkAsyncnie powinien mieć Asyncpostfiksu, ponieważ nie jest w rzeczywistości asynchroniczny, jest synchroniczny. Po prostu to nazwij DoPollingThenWork. Zmniejszy to zamieszanie wśród czytelników.

Servy
źródło
dzięki, ale proszę, powiedz mi, co się stanie, gdy określimy liczbę wątków do uruchomienia, powiedzmy 10. kiedy jeden z 10 wątków kończy się, a następnie ponownie ten wątek przeskakuje na zakończenie innych zadań lub wraca do puli? nie jest to zbyt jasne, aby ... więc proszę wyjaśnić, co się stało za sceną.
Mou
@Mou Co nie jest w tym jasne? Kod czeka, aż będzie mniej niż 10 uruchomionych zadań; kiedy są, dodaje kolejny. Kiedy zadanie się kończy, oznacza to, że zostało zakończone. Otóż ​​to.
Servy
jaka jest zaleta określenia liczby wątków do uruchomienia. czy zbyt wiele wątków może utrudniać działanie? jeśli tak, to po co utrudniać ... jeśli uruchomię 50 wątków zamiast 10, to dlaczego wydajność będzie miała znaczenie ... czy możesz wyjaśnić. dzięki
Thomas
4
@Thomas Jeśli masz zbyt wiele współbieżnych wątków, wątki spędzają więcej czasu na przełączaniu kontekstu niż na produktywną pracę. Przepustowość spada wraz ze wzrostem liczby wątków, ponieważ spędzasz coraz więcej czasu na zarządzaniu wątkami zamiast wykonywania pracy, przynajmniej wtedy, gdy liczba wątków znacznie przekroczy liczbę rdzeni na komputerze.
Servy
3
@Servy To część zadania harmonogramu zadań. Zadania! = Wątki. W Thread.Sleeporyginalnym kodzie zdewastowałoby harmonogram zadań. Jeśli nie jesteś asynchroniczny z rdzeniem, nie jesteś asynchroniczny.
Joseph Lennox
53

W przedszkolu za rogiem używają SemaphoreSlim, aby kontrolować, ile dzieci może bawić się w pokoju wf.

Namalowali na podłodze poza pokojem 5 par śladów.

Kiedy przyjeżdżają dzieci, zostawiają buty na wolnych śladach i wchodzą do pokoju.

Gdy skończą grać, wychodzą, zbierają buty i „uwalniają” miejsce dla innego dziecka.

Jeśli dziecko przyjeżdża i nie ma żadnych śladów, idzie pobawić się gdzie indziej lub po prostu zostaje na chwilę i od czasu do czasu sprawdza (tj. Brak priorytetów FIFO).

Kiedy nauczyciel jest w pobliżu, „wypuszcza” dodatkowy rząd 5 śladów po drugiej stronie korytarza, tak że 5 więcej dzieci może bawić się w pokoju w tym samym czasie.

Ma też te same „pułapki” SemaphoreSlim ...

Jeśli dziecko zakończy grę i wyjdzie z pokoju bez zabrania butów (nie powoduje to „wypuszczenia”), to slot pozostaje zablokowany, mimo że teoretycznie jest pusty. Jednak dzieciak zwykle jest wytykany.

Czasami jedno lub dwoje podstępnych dzieciaków chowa swoje buty w innym miejscu i wchodzi do pokoju, nawet jeśli wszystkie ślady są już zabrane (tzn. SemaphoreSlim nie kontroluje „naprawdę” liczby dzieci w pokoju).

Zwykle nie kończy się to dobrze, ponieważ przepełnienie pokoju kończy się płaczem dzieci i całkowitym zamknięciem pokoju przez nauczyciela.

dandiez
źródło
3
Tego typu odpowiedzi są moimi ulubionymi.
My Stack Overfloweth
OMG, to jest pouczające i zabawne, jak cholera!
Zonus
7

Chociaż zgadzam się, że to pytanie naprawdę odnosi się do scenariusza blokady odliczania, pomyślałem, że warto udostępnić ten link, który odkryłem dla tych, którzy chcą używać SemaphoreSlim jako prostej blokady asynchronicznej. Pozwala na użycie instrukcji using, która może uczynić kodowanie czystszym i bezpieczniejszym.

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

Zamieniłem się _isDisposed=truei _semaphore.Release()zmieniłem w jego Dispose, na wypadek, gdyby w jakiś sposób został wezwany wiele razy.

Należy również zauważyć, że SemaphoreSlim nie jest blokadą ponownego wejścia, co oznacza, że ​​jeśli ten sam wątek wywołuje WaitAsync wiele razy, licznik semafora jest zmniejszany za każdym razem. W skrócie SemaphoreSlim nie jest świadomy wątku.

Jeśli chodzi o pytania dotyczące jakości kodu, lepiej jest umieścić wydanie na końcu próby, aby mieć pewność, że zawsze zostanie wydane.

Pasztet Andrzejkowy
źródło
6
Nie zaleca się publikowania odpowiedzi zawierających tylko linki, ponieważ linki mają tendencję do obumierania z czasem, co czyni odpowiedź bezwartościową. Jeśli możesz, najlepiej podsumuj kluczowe punkty lub kluczowy blok kodu w swojej odpowiedzi.
John