Pourquoi Thread.Sleep est-il si nocif?

128

Je vois souvent qu'il est mentionné que cela Thread.Sleep();ne devrait pas être utilisé, mais je ne comprends pas pourquoi. Si cela Thread.Sleep();peut causer des problèmes, existe-t-il des solutions alternatives avec le même résultat qui seraient sûres?

par exemple.

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

un autre est:

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);
    }
}
Burimi
la source
3
Un résumé du blog pourrait être «ne pas abuser de Thread.sleep ()».
Martin James
5
Je ne dirais pas que c'est dangereux. Je dirais plutôt que c'est comme goto:s'il y a probablement une meilleure solution à vos problèmes que Sleep.
Par défaut le
9
Ce n'est pas exactement la même chose que goto, qui ressemble plus à une odeur de code qu'à une odeur de design. Il n'y a rien de mal à ce qu'un compilateur insère des gotos dans votre code: l'ordinateur ne se confond pas. Mais ce Thread.Sleepn'est pas exactement la même chose; les compilateurs n'insèrent pas cet appel et cela a d'autres conséquences négatives. Mais oui, le sentiment général selon lequel son utilisation est erronée car il y a presque toujours une meilleure solution est certainement correct.
Cody Gray
32
Tout le monde donne des opinions sur les raisons pour lesquelles les exemples ci-dessus sont mauvais, mais personne n'a fourni une version réécrite qui n'utilise pas Thread.Sleep () qui remplit toujours l'objectif des exemples donnés.
StingyJack
3
Chaque fois que vous mettez sleep()du code (ou des tests), un chiot meurt
Reza S

Réponses:

163

Les problèmes liés à l'appel Thread.Sleepsont expliqués assez succinctement ici :

Thread.Sleepa son utilité: simuler de longues opérations lors des tests / débogage sur un thread MTA. Dans .NET, il n'y a aucune autre raison de l'utiliser.

Thread.Sleep(n)signifie bloquer le thread actuel pendant au moins le nombre de tranches de temps (ou quantums de thread) qui peuvent se produire en quelques n millisecondes. La longueur d'une tranche de temps est différente sur différentes versions / types de Windows et différents processeurs et varie généralement de 15 à 30 millisecondes. Cela signifie que le thread est presque garanti de bloquer pendant plus de nmillisecondes. La probabilité que votre thread se réveille exactement après quelques nmillisecondes est à peu près aussi impossible que impossible. Donc, il Thread.Sleepest inutile de chronométrer .

Les threads sont une ressource limitée, ils nécessitent environ 200 000 cycles pour être créés et environ 100 000 cycles pour être détruits. Par défaut, ils réservent 1 mégaoctet de mémoire virtuelle pour sa pile et utilisent 2 000 à 8 000 cycles pour chaque changement de contexte. Cela fait de tout fil d'attente un énorme gaspillage.

La solution préférée: WaitHandles

L'erreur la plus commise est d'utiliser Thread.Sleepavec une construction while ( démo et réponse , belle entrée de blog )

EDIT:
Je voudrais améliorer ma réponse:

Nous avons 2 cas d'utilisation différents:

  1. Nous attendons car nous connaissons un laps de temps précis où nous devons continuer (utilisation Thread.Sleep, System.Threading.Timerou similaire)

  2. Nous attendons car certaines conditions changent quelque temps ... le (s) mot (s) clé (s) est / sont un certain temps ! si le contrôle de condition est dans notre domaine de code, nous devrions utiliser WaitHandles - sinon le composant externe devrait fournir une sorte de hooks ... sinon, sa conception est mauvaise!

Ma réponse couvre principalement le cas d'utilisation 2

Andreas Niedermair
la source
30
Je n'appellerais pas 1 Mo de mémoire un énorme gaspillage compte tenu du matériel d'aujourd'hui
Par défaut le
14
@Default hé, discutez-en avec l'auteur original :) et, cela dépend toujours de votre code - ou mieux: le facteur ... et le problème principal est "Les threads sont une ressource limitée" - les enfants d'aujourd'hui ne savent pas grand-chose sur l'efficacité et le coût de certaines implémentations, parce que "le matériel est bon marché" ... mais parfois vous devez coder très optd
Andreas Niedermair
11
«Cela fait de tout fil d'attente un énorme gaspillage» hein? Si certaines spécifications de protocole exigent une pause d'une seconde avant de continuer, qu'est-ce qui va attendre 1 seconde? Un fil, quelque part, va devoir attendre! La surcharge pour la création / destruction de threads n'est souvent pas pertinente car un thread doit de toute façon être déclenché pour d'autres raisons et il s'exécute pendant toute la durée de vie du processus. Je serais intéressé de voir un moyen d'éviter les changements de contexte lorsqu'une spécification dit «après avoir allumé la pompe, attendez au moins dix secondes que la pression se stabilise avant d'ouvrir la vanne d'alimentation».
Martin James
9
@CodyGray - J'ai relu le message. Je ne vois aucun poisson de quelque couleur que ce soit dans mes commentaires. Andreas s'est retiré du Web: 'Thread.Sleep a son utilité: simuler de longues opérations lors de tests / débogages sur un thread MTA. Dans .NET, il n'y a aucune autre raison de l'utiliser ». Je soutiens qu'il existe de nombreuses applications où un appel sleep () est exactement ce qui est nécessaire. Si les leigons des développeurs, (car il y en a beaucoup), insistent pour utiliser les boucles sleep () comme moniteurs de condition qui devraient être remplacés par events / condvars / semas / peu importe, ce n'est pas une justification pour une affirmation selon laquelle `` il n'y a pas d'autre raison de l'utiliser ».
Martin James
8
En 30 ans de développement d'applications multi-threadées (principalement C ++ / Delphi / Windows), je n'ai jamais vu le besoin de boucles sleep (0) ou sleep (1) dans aucun code livrable. Parfois, j'ai poussé un tel code à des fins de débogage, mais il n'est jamais arrivé au client. «si vous écrivez quelque chose qui ne contrôle pas complètement chaque thread» - la micro-gestion des threads est une erreur aussi grave que la micro-gestion du personnel de développement. La gestion des threads est la raison d'être du système d'exploitation - les outils qu'il fournit doivent être utilisés.
Martin James
34

SCÉNARIO 1 - attendre la fin de la tâche asynchrone: je suis d'accord que WaitHandle / Auto | ManualResetEvent doit être utilisé dans le scénario où un thread attend la fin de la tâche sur un autre thread.

SCENARIO 2 - timing while loop: Cependant, comme un mécanisme de timing brut (while + Thread.Sleep) est parfaitement bien pour 99% des applications qui ne nécessitent PAS de savoir exactement quand le Thread bloqué doit "se réveiller *. L'argument qu'il prend 200k cycles pour créer le thread sont également invalides - le thread de la boucle de synchronisation doit quand même être créé et 200k cycles est juste un autre grand nombre (dites-moi combien de cycles pour ouvrir un fichier / socket / db appels?).

Donc, si while + Thread.Sleep fonctionne, pourquoi compliquer les choses? Seuls les juristes de la syntaxe seraient pratiques !

Swab.Jat
la source
Heureusement, nous avons maintenant TaskCompletionSource pour attendre efficacement un résultat.
Austin Salgat
14

Je voudrais répondre à cette question du point de vue de la politique de codage, qui peut être utile ou non à quiconque. Mais en particulier lorsque vous avez affaire à des outils destinés aux programmeurs d'entreprise 9 à 5, les personnes qui rédigent de la documentation ont tendance à utiliser des mots tels que «ne devrait pas» et «ne jamais» signifier «ne faites pas cela à moins que vous ne sachiez vraiment ce que vous faites et pourquoi ".

Quelques-uns de mes autres favoris dans le monde C # sont qu'ils vous disent de "ne jamais appeler lock (this)" ou "ne jamais appeler GC.Collect ()". Ces deux sont déclarés avec force dans de nombreux blogs et dans la documentation officielle, et l'OMI est une désinformation complète. À un certain niveau, cette désinformation sert son objectif, en ce qu'elle empêche les débutants de faire des choses qu'ils ne comprennent pas avant de rechercher complètement les alternatives, mais en même temps, il est difficile de trouver de VRAIES informations via les moteurs de recherche qui semblent pointer vers des articles vous disant de ne pas faire quelque chose tout en n'offrant aucune réponse à la question "pourquoi pas?"

Politiquement, cela se résume à ce que les gens considèrent comme «bon design» ou «mauvais design». La documentation officielle ne doit pas dicter la conception de mon application. S'il y a vraiment une raison technique pour laquelle vous ne devriez pas appeler sleep (), alors la documentation de l'OMI devrait indiquer qu'il est tout à fait acceptable de l'appeler dans des scénarios spécifiques, mais peut-être offrir des solutions alternatives indépendantes du scénario ou plus appropriées pour l'autre. scénarios.

Appeler clairement "sleep ()" est utile dans de nombreuses situations où les délais sont clairement définis en temps réel, cependant, il existe des systèmes plus sophistiqués pour attendre et signaler les threads qui doivent être pris en compte et compris avant de commencer à dormir ( ) dans votre code, et lancer des instructions sleep () inutiles dans votre code est généralement considéré comme une tactique pour les débutants.

Jason Nelson
la source
5

C'est la boucle 1) .spinning et 2) .polling de vos exemples que les gens mettent en garde , pas la partie Thread.Sleep (). Je pense que Thread.Sleep () est généralement ajouté pour améliorer facilement le code qui tourne ou dans une boucle d'interrogation, donc il est simplement associé à du «mauvais» code.

De plus, les gens font des choses comme:

while(inWait)Thread.Sleep(5000); 

où la variable inWait n'est pas accessible de manière thread-safe, ce qui pose également des problèmes.

Ce que les programmeurs veulent voir, ce sont les threads contrôlés par les constructions Events et Signalisation et Verrouillage, et lorsque vous faites cela, vous n'aurez pas besoin de Thread.Sleep (), et les préoccupations concernant l'accès aux variables thread-safe sont également éliminées. À titre d'exemple, pourriez-vous créer un gestionnaire d'événements associé à la classe FileSystemWatcher et utiliser un événement pour déclencher votre deuxième exemple au lieu d'une boucle?

Comme l'a mentionné Andreas N., lisez Threading en C #, de Joe Albahari , c'est vraiment très bien.

Mike
la source
Alors, quelle est l'alternative à ce qui est dans votre exemple de code?
shinzou
kuhaku - Oui, bonne question. Evidemment, Thread.Sleep () est un raccourci, et parfois un gros raccourci. Pour l'exemple ci-dessus, le reste du code dans la fonction sous la boucle d'interrogation "while (inWait) Thread.Sleep (5000);" est une nouvelle fonction. Cette nouvelle fonction est un délégué (fonction de rappel) et vous la transmettez à tout ce qui définit le drapeau "inWait", et au lieu de changer le drapeau "inWait", le rappel est appelé. C'était l'exemple le plus court que j'ai pu trouver: myelin.co.nz/notes/callbacks/cs-delegates.html
mike
Juste pour vous assurer que je l'ai bien compris, vous voulez emballer Thread.Sleep()avec une autre fonction et l'appeler dans la boucle while?
shinzou
5

Sleep est utilisé dans les cas où des programmes indépendants sur lesquels vous n'avez aucun contrôle peuvent parfois utiliser une ressource couramment utilisée (par exemple, un fichier), à laquelle votre programme doit accéder lorsqu'il s'exécute et lorsque la ressource est utilisée par ces derniers. d'autres programmes, votre programme ne peut pas l'utiliser. Dans ce cas, où vous accédez à la ressource dans votre code, vous placez votre accès à la ressource dans un try-catch (pour attraper l'exception lorsque vous ne pouvez pas accéder à la ressource), et vous mettez cela dans une boucle while. Si la ressource est gratuite, le sommeil n'est jamais appelé. Mais si la ressource est bloquée, vous dormez pendant un laps de temps approprié et essayez à nouveau d'accéder à la ressource (c'est pourquoi vous faites une boucle). Cependant, gardez à l'esprit que vous devez mettre une sorte de limiteur sur la boucle, donc ce n'est pas une boucle potentiellement infinie.

Steve Greene
la source
3

J'ai un cas d'utilisation que je ne vois pas tout à fait couvert ici, et je soutiendrai que c'est une raison valable d'utiliser Thread.Sleep ():

Dans une application console exécutant des tâches de nettoyage, je dois effectuer un grand nombre d'appels de base de données assez coûteux, vers une base de données partagée par des milliers d'utilisateurs simultanés. Afin de ne pas marteler la base de données et d'exclure les autres pendant des heures, j'aurai besoin d'une pause entre les appels, de l'ordre de 100 ms. Ceci n'est pas lié au timing, mais simplement à l'accès à la base de données pour d'autres threads.

Dépenser 2000-8000 cycles sur la commutation de contexte entre les appels qui peuvent prendre 500 ms pour s'exécuter est bénin, tout comme le fait d'avoir 1 Mo de pile pour le thread, qui s'exécute comme une seule instance sur un serveur.

Henrik R Clausen
la source
Oui, utiliser Thread.Sleepdans une application console à un seul thread et à usage unique comme celle que vous décrivez est parfaitement OK.
Theodor Zoulias
-7

Je suis d'accord avec beaucoup ici, mais je pense aussi que cela dépend.

Récemment, j'ai fait ce code:

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();
}

C'était une simple fonction d'animation, et je l'ai utilisée Thread.Sleep.

Ma conclusion, si cela fait le travail, utilisez-le.


la source
-8

Pour ceux d'entre vous qui n'ont pas vu d'argument valable contre l'utilisation de Thread.Sleep dans SCENARIO 2, il y en a vraiment un - la sortie de l'application est bloquée par la boucle while (SCENARIO 1/3 est tout simplement stupide, donc pas digne de plus mentionnant)

Beaucoup de ceux qui prétendent être au courant, criant Thread.leep is evil n'ont pas mentionné une seule raison valable pour ceux d'entre nous qui exigeaient une raison pratique de ne pas l'utiliser - mais la voici, grâce à Pete - Thread. is Evil (peut être facilement évité avec un timer / handler)

    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;
    }
Swab.Jat
la source
9
-, non, ce Thread.Sleepn'est pas la raison (le nouveau thread et la boucle while continue est)! vous pouvez simplement supprimer la Thread.Sleep-line - et voilà: le programme ne se fermera pas aussi ...
Andreas Niedermair