Perché Thread.Sleep è così dannoso


128

Lo vedo spesso menzionato che Thread.Sleep();non dovrebbe essere usato, ma non riesco a capire perché sia ​​così. Se Thread.Sleep();può causare problemi, esistono soluzioni alternative con lo stesso risultato che sarebbero sicure?

per esempio.

while(true)
{
    doSomework();
    i++;
    Thread.Sleep(5000);
}

un altro è:

while (true)
{
    string[] images = Directory.GetFiles(@"C:\Dir", "*.png");

    foreach (string image in images)
    {
        this.Invoke(() => this.Enabled = true);
        pictureBox1.Image = new Bitmap(image);
        Thread.Sleep(1000);
    }
}

3
Un riepilogo del blog potrebbe essere "non abusare di Thread.sleep ()".
Martin James,

5
Non direi che sia dannoso. Preferirei dire che è come, ad goto:esempio, probabilmente c'è una soluzione migliore ai tuoi problemi rispetto a Sleep.
Predefinito

9
Non è esattamente lo stesso di goto, che è più simile a un odore di codice che a un odore di design. Non c'è niente di sbagliato con un compilatore che inserisce gotos nel tuo codice: il computer non si confonde. Ma Thread.Sleepnon è esattamente la stessa cosa; i compilatori non inseriscono quella chiamata e ha altre conseguenze negative. Ma sì, l'opinione generale secondo cui usarlo è sbagliato perché c'è quasi sempre una soluzione migliore è certamente corretta.
Cody Grey

32
Tutti forniscono opinioni sul motivo per cui gli esempi precedenti sono pessimi, ma nessuno ha fornito una versione riscritta che non utilizza Thread.Sleep () che raggiunge comunque l'obiettivo degli esempi forniti.
StingyJack

3
Ogni volta che inserisci un sleep()codice (o fai dei test) un cucciolo muore
Reza S

Risposte:


163

I problemi con la chiamata Thread.Sleepsono spiegati abbastanza succintamente qui :

Thread.Sleepha il suo uso: simulare lunghe operazioni durante il test / debug su un thread MTA. In .NET non c'è altro motivo per usarlo.

Thread.Sleep(n)significa bloccare il thread corrente per almeno il numero di timeslice (o quantum del thread) che possono verificarsi entro n millisecondi. La lunghezza di un timeslice è diversa su diverse versioni / tipi di Windows e diversi processori e generalmente varia da 15 a 30 millisecondi. Ciò significa che è quasi garantito che il thread si blocchi per più di nmillisecondi. La probabilità che il tuo thread si riattivi esattamente dopo nmillisecondi è impossibile quanto impossibile. Quindi, Thread.Sleepè inutile per il tempismo .

I thread sono una risorsa limitata, richiedono circa 200.000 cicli per essere creati e circa 100.000 cicli per essere distrutti. Per impostazione predefinita, riservano 1 megabyte di memoria virtuale per il proprio stack e utilizzano 2.000-8.000 cicli per ogni cambio di contesto. Questo rende qualsiasi thread in attesa un enorme spreco.

La soluzione preferita: WaitHandles

L'errore più commesso è usare Thread.Sleepcon un costrutto while ( demo e risposta , bel post sul blog )

EDIT:
vorrei migliorare la mia risposta:

Abbiamo 2 diversi casi d'uso:

  1. Siamo in attesa, perché sappiamo che una specifica periodo in cui dobbiamo continuare (uso Thread.Sleep, System.Threading.Timero alike)

  2. Stiamo aspettando perché qualche condizione cambia da tempo ... parola / e chiave / e è / sono da tempo ! se il controllo delle condizioni è nel nostro dominio di codice, dovremmo usare WaitHandles - altrimenti il ​​componente esterno dovrebbe fornire una sorta di hook ... se non lo fa il suo design è cattivo!

La mia risposta copre principalmente il caso d'uso 2


30
Non definirei 1 MB di memoria un enorme spreco considerando l'hardware di oggi
predefinito

14
@Default hey, discutine con l'autore originale :) e, dipende sempre dal tuo codice - o meglio: il fattore ... e il problema principale è "I thread sono una risorsa limitata" - i bambini di oggi non sanno molto sull'efficienza e il costo di alcune implementazioni, perché "l'hardware è economico" ... ma a volte è necessario programmare molto bene
Andreas Niedermair

11
"Questo rende qualsiasi filo in attesa un enorme spreco" eh? Se alcune specifiche di protocollo richiedono una pausa di un secondo prima di continuare, cosa aspetterà per 1 secondo? Qualche filo, da qualche parte, dovrà aspettare! Il sovraccarico per la creazione / distruzione del thread è spesso irrilevante perché un thread deve essere generato comunque per altri motivi e viene eseguito per la durata del processo. Sarei interessato a vedere un modo per evitare i cambi di contesto quando una specifica dice "dopo aver acceso la pompa, attendere almeno dieci secondi affinché la pressione si stabilizzi prima di aprire la valvola di alimentazione".
Martin James,

9
@CodyGray - Ho letto di nuovo il post. Non vedo pesci di alcun colore nei miei commenti. Andreas ha tirato fuori dal web: 'Thread.Sleep ha il suo uso: simulare lunghe operazioni durante il test / debug su un thread MTA. In .NET non c'è altro motivo per usarlo '. Sostengo che ci sono molte app in cui una chiamata sleep () è, beh, proprio ciò che è richiesto. Se i leigoni degli sviluppatori, (perché ce ne sono molti), insistono nell'usare i cicli sleep () come monitor delle condizioni che dovrebbero essere sostituiti con eventi / condvars / semas / qualunque cosa, non è una giustificazione per l'affermazione che 'non c'è nessun altro motivo per usarlo '.
Martin James,

8
In 30 anni di sviluppo di app multiThread, (principalmente C ++ / Delphi / Windows), non ho mai visto alcuna necessità di cicli sleep (0) o sleep (1) in nessun codice consegnabile. Di tanto in tanto, ho inserito tale codice per scopi di debug, ma non è mai arrivato al cliente. "se stai scrivendo qualcosa che non controlla completamente ogni thread": la microgestione dei thread è un grosso errore quanto la microgestione del personale di sviluppo. La gestione dei thread è ciò per cui il sistema operativo è lì: gli strumenti che fornisce dovrebbero essere usati.
Martin James

34

SCENARIO 1 - Attendi il completamento dell'attività asincrona: accetto che WaitHandle / Auto | ManualResetEvent debba essere utilizzato nello scenario in cui un thread è in attesa del completamento dell'attività su un altro thread.

SCENARIO 2 - timing while loop: Tuttavia, come un meccanismo di timing grezzo (while + Thread.Sleep) va perfettamente bene per il 99% delle applicazioni che NON richiedono di sapere esattamente quando il Thread bloccato dovrebbe "svegliarsi *. L'argomento che prende Anche 200k cicli per creare il thread non sono validi: il thread del ciclo di temporizzazione deve essere creato comunque e 200k cicli è solo un altro grande numero (dimmi quanti cicli per aprire un file / socket / chiamate db?).

Quindi se while + Thread.Sleep funziona, perché complicare le cose? Solo gli avvocati della sintassi sarebbero pratici !


Per fortuna ora abbiamo TaskCompletionSource per attendere in modo efficiente un risultato.
Austin Salgat

14

Vorrei rispondere a questa domanda da una prospettiva di coding-policy, che può essere utile o meno a chiunque. Ma in particolare quando hai a che fare con strumenti destinati a programmatori aziendali 9-5, le persone che scrivono documentazione tendono a usare parole come "non dovrebbe" e "mai" per significare "non farlo a meno che tu non sappia veramente cosa vuoi stai facendo e perché ".

Un paio di altri miei preferiti nel mondo C # sono che ti dicono di "non chiamare mai lock (this)" o "non chiamare mai GC.Collect ()". Questi due sono dichiarati con forza in molti blog e documentazione ufficiale, e IMO sono una completa disinformazione. Ad un certo livello questa disinformazione serve al suo scopo, in quanto impedisce ai principianti di fare cose che non capiscono prima di ricercare completamente le alternative, ma allo stesso tempo rende difficile trovare informazioni REALI tramite motori di ricerca che tutti sembrano indicare articoli che ti dicono di non fare qualcosa senza offrire alcuna risposta alla domanda "perché no?"

Politicamente, si riduce a ciò che le persone considerano "buon design" o "cattivo design". La documentazione ufficiale non dovrebbe dettare il design della mia applicazione. Se c'è davvero una ragione tecnica per cui non dovresti chiamare sleep (), allora IMO la documentazione dovrebbe affermare che è assolutamente corretto chiamarlo in scenari specifici, ma forse offrire alcune soluzioni alternative che sono indipendenti dallo scenario o più appropriate per l'altro scenari.

Chiaramente chiamare "sleep ()" è utile in molte situazioni in cui le scadenze sono chiaramente definite in termini di tempo reale, tuttavia, ci sono sistemi più sofisticati per l'attesa e la segnalazione dei thread che dovrebbero essere considerati e compresi prima di iniziare a gettare sleep ( ) nel codice e il lancio di istruzioni sleep () non necessarie nel codice è generalmente considerata una tattica per principianti.


5

È il ciclo 1) .spinning e 2) .polling dei tuoi esempi contro cui le persone mettono in guardia , non la parte Thread.Sleep (). Penso che Thread.Sleep () venga solitamente aggiunto per migliorare facilmente il codice in rotazione o in un ciclo di polling, quindi è semplicemente associato a codice "cattivo".

Inoltre le persone fanno cose come:

while(inWait)Thread.Sleep(5000); 

dove la variabile inWait non è accessibile in modo thread-safe, il che causa anche problemi.

Ciò che i programmatori vogliono vedere sono i thread controllati dai costrutti Events e Signaling e Locking, e quando lo fai non avrai bisogno di Thread.Sleep (), e anche le preoccupazioni sull'accesso alle variabili thread-safe vengono eliminate. Ad esempio, potresti creare un gestore di eventi associato alla classe FileSystemWatcher e utilizzare un evento per attivare il tuo secondo esempio invece di eseguire il ciclo?

Come ha detto Andreas N., leggi Threading in C #, di Joe Albahari , è davvero molto buono.


Allora qual è l'alternativa a fare ciò che è nel tuo esempio di codice?
shinzou

kuhaku - Sì, bella domanda. Ovviamente Thread.Sleep () è una scorciatoia, e talvolta una grande scorciatoia. Per l'esempio precedente, il resto del codice nella funzione sotto il ciclo di polling "while (inWait) Thread.Sleep (5000);" è una nuova funzione. Quella nuova funzione è un delegato (funzione di callback) e la si passa a qualunque cosa stia impostando il flag "inWait" e invece di modificare il flag "inWait", viene richiamato il callback. Questo è stato l'esempio più breve che ho trovato: myelin.co.nz/notes/callbacks/cs-delegates.html
mike

Solo per assicurarti di averlo capito, intendi avvolgere Thread.Sleep()con un'altra funzione e chiamarla nel ciclo while?
shinzou

5

Lo stato di sospensione viene utilizzato nei casi in cui programmi indipendenti su cui non si ha alcun controllo possono talvolta utilizzare una risorsa di uso comune (ad esempio un file), a cui il programma deve accedere quando viene eseguito e quando la risorsa è utilizzata da questi altri programmi il tuo programma non può utilizzarlo. In questo caso, dove accedi alla risorsa nel tuo codice, metti il ​​tuo accesso alla risorsa in un try-catch (per catturare l'eccezione quando non puoi accedere alla risorsa) e lo metti in un ciclo while. Se la risorsa è gratuita, il sonno non viene mai chiamato. Ma se la risorsa è bloccata, dormi per un periodo di tempo appropriato e tenti di accedere nuovamente alla risorsa (questo perché stai eseguendo il loop). Tuttavia, tieni presente che devi mettere una sorta di limitatore sul loop, quindi non è un loop potenzialmente infinito.


3

Ho un caso d'uso che non vedo esattamente coperto qui e sosterrò che questo è un motivo valido per usare Thread.Sleep ():

In un'applicazione console che esegue lavori di pulizia, è necessario effettuare una grande quantità di chiamate al database piuttosto costose, a un database condiviso da migliaia di utenti simultanei. Per non martellare il DB ed escludere altri per ore, avrò bisogno di una pausa tra le chiamate, nell'ordine di 100 ms. Ciò non è correlato alla tempistica, ma solo alla concessione dell'accesso al DB per altri thread.

Spendere 2000-8000 cicli per il cambio di contesto tra le chiamate che possono richiedere 500 ms per l'esecuzione è benigno, così come avere 1 MB di stack per il thread, che viene eseguito come singola istanza su un server.


Sì, l'utilizzo Thread.Sleepin un'applicazione Console a thread singolo e monofunzione come quella che descrivi è perfettamente OK.
Theodor Zoulias

-7

Sono d'accordo con molti qui, ma penso anche che dipenda.

Recentemente ho fatto questo codice:

private void animate(FlowLayoutPanel element, int start, int end)
{
    bool asc = end > start;
    element.Show();
    while (start != end) {
        start += asc ? 1 : -1;
        element.Height = start;
        Thread.Sleep(1);
    }
    if (!asc)
    {
        element.Hide();
    }
    element.Focus();
}

Era una semplice funzione di animazione e l'ho usata Thread.Sleep.

La mia conclusione, se funziona, usala.


-8

Per quelli di voi che non hanno visto un argomento valido contro l'uso di Thread.Sleep in SCENARIO 2, ce n'è davvero uno: l'uscita dell'applicazione viene bloccata dal ciclo while (SCENARIO 1/3 è semplicemente stupido quindi non degno di più menzionare)

Molti che fingono di essere in-the-know, urlando Thread.Sleep is evil non hanno menzionato un unico motivo valido per quelli di noi che chiedevano una ragione pratica per non usarlo - ma eccolo qui, grazie a Pete - Thread.Sleep. è il male (può essere facilmente evitato con un timer / gestore)

    static void Main(string[] args)
    {
        Thread t = new Thread(new ThreadStart(ThreadFunc));
        t.Start();

        Console.WriteLine("Hit any key to exit.");
        Console.ReadLine();

        Console.WriteLine("App exiting");
        return;
    }

    static void ThreadFunc()
    {
        int i=0;
        try
        {
            while (true)
            {
                Console.WriteLine(Thread.CurrentThread.ThreadState.ToString() + " " + i);

                Thread.Sleep(1000 * 10);
                i++;
            }
        }
        finally
        {
            Console.WriteLine("Exiting while loop");
        }
        return;
    }

9
-, Thread.Sleepno , non è la ragione per questo (il nuovo thread e il ciclo while continuo lo sono)! puoi semplicemente rimuovere la Thread.Sleep-line - et voilà: anche il programma non terminerà ...
Andreas Niedermair
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.