Necessità di comprendere l'utilizzo di SemaphoreSlim


95

Ecco il codice che ho ma non capisco cosa SemaphoreSlimstia facendo.

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

Cosa attende ss.WaitAsync();e ss.Release();fa?

Immagino che se eseguo 50 thread alla volta, scrivo codice in SemaphoreSlim ss = new SemaphoreSlim(10);questo modo sarà costretto a eseguire 10 thread attivi alla volta.

Quando uno dei 10 thread viene completato, verrà avviato un altro thread. Se non ho ragione, aiutami a capire con una situazione di esempio.

Perché è awaitnecessario insieme a ss.WaitAsync();? Cosa fa ss.WaitAsync();?


3
Una cosa da notare è che dovresti davvero racchiudere quel "DoPollingThenWorkAsync ();" in un "try {DoPollingThenWorkAsync ();} finalmente {ss.Release ();}", altrimenti le eccezioni faranno morire di fame quel semaforo.
Austin Salgat

Mi sento un po 'strano che acquisiamo e rilasciamo il semaforo rispettivamente all'esterno / all'interno dell'attività. Spostare "await ss.WaitAsync ()" all'interno dell'attività farà la differenza?
Shane Lu

Risposte:


77

immagino che se eseguo 50 thread alla volta, codice come SemaphoreSlim ss = new SemaphoreSlim (10); forzerà l'esecuzione di 10 thread attivi alla volta

È corretto; l'uso del semaforo garantisce che non ci saranno più di 10 lavoratori che svolgono questo lavoro contemporaneamente.

La chiamata WaitAsyncal semaforo produce un'attività che verrà completata quando a quel thread è stato concesso "l'accesso" a quel token. await-ing tale attività consente al programma di continuare l'esecuzione quando è "consentito" di farlo. Avere una versione asincrona, piuttosto che chiamare Wait, è importante sia per garantire che il metodo rimanga asincrono, piuttosto che essere sincrono, sia per gestire il fatto che unasync metodo può eseguire codice su diversi thread, a causa dei callback, e così via la naturale affinità del thread con i semafori può essere un problema.

Una nota a margine: DoPollingThenWorkAsyncnon dovrebbe avere il Asyncsuffisso perché non è effettivamente asincrono, è sincrono. Chiamalo e basta DoPollingThenWork. Ridurrà la confusione per i lettori.


grazie ma per favore dimmi cosa succede quando specifichiamo il numero di thread da eseguire, diciamo 10.quando uno dei 10 thread finisce e poi di nuovo quel thread salta su finisci un altro lavoro o torna al pool? questo non è molto chiaro per .... quindi per favore spiega cosa succede dietro le quinte.
Mou

@Mou Cosa non è chiaro al riguardo? Il codice attende fino a quando non sono presenti meno di 10 attività attualmente in esecuzione; quando ci sono, ne aggiunge un altro. Quando un'attività termina, indica che è terminata. Questo è tutto.
Servizio

qual è il vantaggio di specificare il numero di thread da eseguire. se troppi thread possono ostacolare le prestazioni? se sì, perché ostacolare ... se eseguo 50 thread invece di 10 thread, allora perché le prestazioni saranno importanti ... puoi spiegare. grazie
Thomas

4
@Thomas Se hai troppi thread simultanei, i thread impiegano più tempo a cambiare contesto di quanto impieghino a svolgere un lavoro produttivo. Il throughput diminuisce all'aumentare dei thread mentre si spende sempre più tempo a gestire i thread invece di lavorare, almeno una volta che il conteggio dei thread supera di molto il numero di core sulla macchina.
Servizio

3
@Servy Fa parte del lavoro dell'utilità di pianificazione. Attività! = Thread. Il Thread.Sleepcodice originale avrebbe devastato l'utilità di pianificazione. Se non sei asincrono fino in fondo, non sei asincrono.
Joseph Lennox

67

Nell'asilo dietro l'angolo usano un SemaphoreSlim per controllare quanti bambini possono giocare nella stanza di educazione fisica.

Hanno dipinto sul pavimento, fuori dalla stanza, 5 paia di impronte.

Quando i bambini arrivano, lasciano le scarpe su un paio di impronte libere ed entrano nella stanza.

Una volta finito di giocare, escono, raccolgono le scarpe e "rilasciano" uno slot per un altro bambino.

Se un bambino arriva e non sono rimaste impronte, va a giocare altrove o resta in giro per un po 'e controlla ogni tanto (cioè, nessuna priorità FIFO).

Quando un insegnante è nei paraggi, "rilascia" una fila extra di 5 impronte sull'altro lato del corridoio in modo che altri 5 bambini possano giocare nella stanza contemporaneamente.

Ha anche le stesse "insidie" di SemaphoreSlim ...

Se un bambino finisce di giocare e lascia la stanza senza raccogliere le scarpe (non fa scattare il "rilascio") allora lo slot rimane bloccato, anche se teoricamente c'è uno slot vuoto. Il ragazzo di solito viene sgridato, però.

A volte uno o due bambini subdoli nascondono le scarpe altrove ed entrano nella stanza, anche se tutte le impronte sono già state prese (cioè, il SemaphoreSlim non controlla "realmente" quanti bambini ci sono nella stanza).

Questo di solito non finisce bene, poiché il sovraffollamento della stanza tende a finire con i bambini che piangono e l'insegnante chiude completamente la stanza.


9
Questo tipo di risposte sono le mie preferite.
mio stack trabocca il

7

Sebbene accetto che questa domanda si riferisca davvero a uno scenario di blocco del conto alla rovescia, ho pensato che valesse la pena condividere questo collegamento che ho scoperto per coloro che desiderano utilizzare un SemaphoreSlim come un semplice blocco asincrono. Ti consente di utilizzare l'istruzione using che potrebbe rendere la codifica più ordinata e sicura.

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

Ho scambiato _isDisposed=truee _semaphore.Release()girato nel suo Dispose anche se in qualche modo è stato chiamato più volte.

Inoltre è importante notare che SemaphoreSlim non è un blocco rientrante, il che significa che se lo stesso thread chiama WaitAsync più volte, il conteggio del semaforo viene decrementato ogni volta. In breve SemaphoreSlim non è a conoscenza del thread.

Per quanto riguarda le domande sulla qualità del codice, è meglio mettere la versione entro la fine di una prova per assicurarsi che venga sempre rilasciata.


6
Non è consigliabile pubblicare risposte di solo collegamento poiché i collegamenti tendono a morire nel tempo, rendendo così la risposta inutile. Se puoi, è meglio riassumere i punti chiave o il blocco del codice chiave nella tua risposta.
John
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.