Sie müssen die Verwendung von SemaphoreSlim verstehen

90

Hier ist der Code, den ich habe, aber ich verstehe nicht, was er SemaphoreSlimtut.

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

Was erwartet ss.WaitAsync();und ss.Release();tut?

Ich denke, wenn ich 50 Threads gleichzeitig laufen lasse und dann Code schreibe SemaphoreSlim ss = new SemaphoreSlim(10);, wird er gezwungen sein, 10 aktive Threads gleichzeitig auszuführen.

Wenn einer von 10 Threads abgeschlossen ist, wird ein anderer Thread gestartet. Wenn ich nicht richtig liege, helfen Sie mir, die Beispielsituation zu verstehen.

Warum wird awaitzusammen mit benötigt ss.WaitAsync();? Was macht ss.WaitAsync();das

Mou
quelle
3
Eine Sache zu beachten ist, dass Sie wirklich das "DoPollingThenWorkAsync ();" in einem "try {DoPollingThenWorkAsync ();} finally {ss.Release ();}", andernfalls verhungern Ausnahmen dieses Semaphor dauerhaft.
Austin Salgat
Ich finde es etwas seltsam, dass wir das Semaphor außerhalb bzw. innerhalb der Aufgabe erwerben und freigeben. Wird das Verschieben von "await ss.WaitAsync ()" innerhalb der Aufgabe einen Unterschied machen?
Shane Lu

Antworten:

73

Ich denke, wenn ich 50 Thread gleichzeitig laufen lasse, dann Code wie SemaphoreSlim ss = new SemaphoreSlim (10); zwingt, 10 aktive Threads gleichzeitig auszuführen

Das ist richtig; Durch die Verwendung des Semaphors wird sichergestellt, dass nicht mehr als 10 Mitarbeiter gleichzeitig diese Arbeit ausführen.

Das Aufrufen WaitAsyncdes Semaphors erzeugt eine Aufgabe, die abgeschlossen wird, wenn diesem Thread "Zugriff" auf dieses Token gewährt wurde. awaitWenn Sie diese Aufgabe ausführen, kann das Programm die Ausführung fortsetzen, wenn dies "erlaubt" ist. Es Waitist wichtig, eine asynchrone Version zu haben, anstatt sie aufzurufen , um sicherzustellen, dass die Methode asynchron bleibt und nicht synchron ist, und asyncum zu berücksichtigen , dass eine Methode aufgrund der Rückrufe Code über mehrere Threads hinweg ausführen kann Die natürliche Fadenaffinität zu Semaphoren kann ein Problem sein.

Eine Randnotiz: DoPollingThenWorkAsyncsollte das AsyncPostfix nicht haben, da es nicht wirklich asynchron ist, sondern synchron. Nenn es einfach DoPollingThenWork. Dies wird die Verwirrung der Leser verringern.

Servieren
quelle
Vielen Dank, aber bitte sagen Sie mir, was passiert, wenn wir angeben, dass kein Thread ausgeführt werden soll, z. Das ist nicht sehr klar für ... also erklären Sie bitte, was hinter den Kulissen passiert.
Mou
@Mou Was ist daran nicht klar? Der Code wartet, bis derzeit weniger als 10 Aufgaben ausgeführt werden. Wenn es welche gibt, fügt es eine weitere hinzu. Wenn eine Aufgabe beendet ist, wird angezeigt, dass sie beendet wurde. Das ist es.
Servy
Was ist der Vorteil der Angabe der Anzahl der auszuführenden Threads? Wenn zu viele Threads die Leistung beeinträchtigen können? Wenn ja, warum dann behindern? Wenn ich 50 Threads anstelle von 10 Threads verwende, warum spielt dann die Leistung eine Rolle? Kannst du das bitte erklären? danke
Thomas
4
@Thomas Wenn Sie zu viele gleichzeitige Threads haben, verbringen die Threads mehr Zeit mit dem Kontextwechsel als mit produktiver Arbeit. Der Durchsatz sinkt, wenn die Threads steigen, da Sie immer mehr Zeit mit der Verwaltung von Threads verbringen, anstatt zu arbeiten, zumindest wenn Ihre Thread-Anzahl weit über die Anzahl der Kerne auf dem Computer hinausgeht.
Servy
3
@Servy Das gehört zum Job des Taskplaners. Aufgaben! = Threads. Das Thread.Sleepim ursprünglichen Code würde den Taskplaner zerstören. Wenn Sie nicht asynchron zum Kern sind, sind Sie nicht asynchron.
Joseph Lennox
53

Im Kindergarten um die Ecke steuern sie mit einem SemaphoreSlim, wie viele Kinder im Sportraum spielen können.

Sie malten auf dem Boden außerhalb des Raumes 5 Paar Fußabdrücke.

Als die Kinder ankommen, lassen sie ihre Schuhe auf einem freien Paar Fußabdrücke und betreten den Raum.

Sobald sie mit dem Spielen fertig sind, kommen sie heraus, sammeln ihre Schuhe und "geben" einen Platz für ein anderes Kind frei.

Wenn ein Kind ankommt und keine Fußspuren mehr vorhanden sind, spielen sie woanders oder bleiben nur eine Weile in der Nähe und überprüfen ab und zu (dh keine FIFO-Prioritäten).

Wenn eine Lehrerin in der Nähe ist, "gibt" sie eine zusätzliche Reihe von 5 Fußabdrücken auf der anderen Seite des Korridors frei, sodass 5 weitere Kinder gleichzeitig im Raum spielen können.

Es hat auch die gleichen "Fallstricke" von SemaphoreSlim ...

Wenn ein Kind mit dem Spielen fertig ist und den Raum verlässt, ohne die Schuhe einzusammeln (löst nicht die "Freigabe" aus), bleibt der Steckplatz blockiert, obwohl theoretisch ein leerer Steckplatz vorhanden ist. Das Kind wird jedoch normalerweise abgesagt.

Manchmal verstecken ein oder zwei hinterhältige Kinder ihre Schuhe woanders und betreten den Raum, selbst wenn alle Fußabdrücke bereits aufgenommen wurden (dh der SemaphoreSlim kontrolliert nicht "wirklich", wie viele Kinder sich im Raum befinden).

Dies endet normalerweise nicht gut, da die Überfüllung des Raums dazu führt, dass Kinder weinen und der Lehrer den Raum vollständig schließt.

Dandiez
quelle
3
Diese Art von Antworten sind meine Favoriten.
Mein Stapel läuft
OMG das ist informativ und lustig zum Teufel!
Zonus
7

Obwohl ich akzeptiere, dass sich diese Frage wirklich auf ein Countdown-Sperrszenario bezieht, hielt ich es für sinnvoll, diesen Link zu teilen, den ich für diejenigen entdeckt habe, die einen SemaphoreSlim als einfache asynchrone Sperre verwenden möchten. Sie können die using-Anweisung verwenden, die die Codierung übersichtlicher und sicherer machen könnte.

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

Ich habe Swap _isDisposed=trueund _semaphore.Release()um in seiner Entsorgen obwohl im Fall irgendwie ist es mehrmals aufgerufen wurde.

Es ist auch wichtig zu beachten, dass SemaphoreSlim keine Wiedereintrittssperre ist. Wenn also derselbe Thread WaitAsync mehrmals aufruft, wird die Anzahl der Semaphore jedes Mal dekrementiert. Kurz gesagt, SemaphoreSlim ist nicht Thread-fähig.

In Bezug auf die Fragen in Bezug auf die Codequalität ist es besser, die Veröffentlichung innerhalb eines endgültigen Versuchs zu platzieren, um sicherzustellen, dass sie immer veröffentlicht wird.

Andrew Pate
quelle
6
Es ist nicht ratsam, Antworten nur mit Links zu veröffentlichen, da Links im Laufe der Zeit absterben und die Antwort daher wertlos wird. Wenn Sie können, fassen Sie die wichtigsten Punkte oder den Schlüsselcodeblock am besten in Ihrer Antwort zusammen.
John