it-swarm.com.de

Warum ist Thread.Sleep so schädlich?

Ich sehe oft, dass erwähnt wird, dass Thread.Sleep(); nicht verwendet werden sollte, aber ich kann nicht verstehen, warum dies so ist. Wenn Thread.Sleep(); Probleme verursachen kann, gibt es alternative Lösungen mit demselben Ergebnis, die sicher wären?

z.B.

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

ein anderer ist:

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);
    }
}
112
Burimi

Die Probleme mit dem Aufruf von Thread.Sleep Werden hier kurz erklärt :

Thread.Sleep Hat seinen Zweck: Langwierige Operationen beim Testen/Debuggen eines MTA-Threads zu simulieren. In .NET gibt es keinen anderen Grund, es zu verwenden.

Thread.Sleep(n) bedeutet, dass der aktuelle Thread für mindestens die Anzahl der Zeitscheiben (oder Threadquanten) blockiert wird, die innerhalb von n Millisekunden auftreten können. Die Länge einer Zeitscheibe ist bei verschiedenen Windows-Versionen/Windows-Typen und verschiedenen Prozessoren unterschiedlich und liegt im Allgemeinen zwischen 15 und 30 Millisekunden. Dies bedeutet, dass der Thread fast garantiert länger als n Millisekunden blockiert. Die Wahrscheinlichkeit, dass Ihr Thread genau nach n Millisekunden wieder aktiviert wird, ist so unmöglich wie unmöglich. Also ist Thread.Sleep Für das Timing sinnlos .

Threads sind eine begrenzte Ressource. Sie benötigen ungefähr 200.000 Zyklen, um sie zu erstellen, und ungefähr 100.000 Zyklen, um sie zu zerstören. Standardmäßig reservieren sie 1 MB virtuellen Speicher für den Stack und verwenden 2.000 bis 8.000 Zyklen für jeden Kontextwechsel. Dies macht jeden wartenden Thread zu einer riesigen Verschwendung.

Die bevorzugte Lösung: WaitHandles

Der am häufigsten gemachte Fehler ist die Verwendung von Thread.Sleep Mit einem while-Konstrukt ( Demo und der Antwort , Netter Blog-Eintrag )

EDIT:
Ich möchte meine Antwort verbessern:

Wir haben 2 verschiedene Anwendungsfälle:

  1. Wir warten, weil wir einen bestimmten Zeitraum kennen, in dem wir fortfahren sollen (verwenden Sie Thread.Sleep, System.Threading.Timer Oder Ähnliches).

  2. Wir warten, weil sich eine Bedingung irgendwann ändert ... Schlüsselwort (e) ist/sind irgendwann ! Wenn sich die Zustandsüberprüfung in unserer Code-Domain befindet, sollten wir WaitHandles verwenden - andernfalls sollte die externe Komponente eine Art Haken bereitstellen ... wenn dies nicht der Fall ist, ist das Design schlecht!

Meine Antwort bezieht sich hauptsächlich auf Anwendungsfall 2

149

SZENARIO 1 - Warten auf den Abschluss einer asynchronen Aufgabe: Ich bin damit einverstanden, dass WaitHandle/Auto | ManualResetEvent in einem Szenario verwendet wird, in dem ein Thread auf den Abschluss einer Aufgabe in einem anderen Thread wartet.

SZENARIO 2 - Timing While-Schleife: Als grobe Timing-Mechanismus (while + Thread.Sleep) ist dies jedoch für 99% der Anwendungen völlig in Ordnung, bei denen NICHT bekannt sein muss gena wann Der blockierte Thread sollte "aufwachen". Das Argument, dass 200.000 Zyklen zum Erstellen des Threads erforderlich sind, ist ebenfalls ungültig - der Timing-Loop-Thread muss trotzdem erstellt werden, und 200.000 Zyklen sind nur eine weitere große Zahl (sagen Sie mir, wie viele Zyklen zum Öffnen einer Datei erforderlich sind/socket/db ruft auf?).

Also, wenn while + Thread.Sleep funktioniert, warum komplizieren die Dinge? Nur Syntax-Anwälte wären praktisch!

32
Swab.Jat

Ich möchte diese Frage aus codierpolitischer Sicht beantworten, die für niemanden hilfreich sein kann oder auch nicht. Vor allem, wenn Sie mit Tools arbeiten, die für 9-5-Unternehmensprogrammierer gedacht sind, verwenden Benutzer, die Dokumentationen schreiben, häufig Wörter wie "sollte nicht" und "nie", um zu bedeuten, dass Sie dies nur tun, wenn Sie wirklich wissen, was Sie tun machen und warum ".

Ein paar meiner anderen Favoriten in der C # -Welt sind, dass Sie "never call lock (this)" oder "never call GC.Collect ()" aufrufen sollen. Diese beiden werden in vielen Blogs und offiziellen Dokumentationen mit Nachdruck deklariert, und IMO sind vollständige Fehlinformationen. In gewisser Weise hat diese Fehlinformation ihren Zweck, indem sie die Anfänger davon abhält, Dinge zu tun, die sie nicht verstehen, bevor sie die Alternativen vollständig erforscht haben, und es gleichzeitig schwierig macht, ECHTE Informationen über Suchmaschinen zu finden scheinen auf Artikel zu verweisen, in denen Sie aufgefordert werden, nichts zu tun, ohne die Frage "Warum nicht?" zu beantworten.

Politisch läuft es darauf hinaus, was die Leute als "gutes Design" oder "schlechtes Design" bezeichnen. Die offizielle Dokumentation sollte nicht das Design meiner Anwendung bestimmen. Wenn es wirklich einen technischen Grund gibt, den Sie nicht sleep () nennen sollten, sollte in der Dokumentation IMO angegeben werden, dass es völlig in Ordnung ist, ihn unter bestimmten Szenarien aufzurufen, aber möglicherweise einige alternative Lösungen anbieten, die szenariounabhängig oder besser für die anderen geeignet sind Szenarien.

Das eindeutige Aufrufen von "sleep ()" ist in vielen Situationen nützlich, in denen die Fristen in Echtzeit klar definiert sind. Es gibt jedoch komplexere Systeme zum Warten und Signalisieren von Threads, die berücksichtigt und verstanden werden sollten, bevor Sie mit dem Schlafen beginnen ( ) in Ihren Code einzufügen und unnötige sleep () - Anweisungen in Ihren Code einzufügen, wird im Allgemeinen als Anfängertaktik betrachtet.

8
Jason Nelson

Ruhezustand wird in Fällen verwendet, in denen unabhängige Programme, über die Sie keine Kontrolle haben, manchmal eine häufig verwendete Ressource (z. B. eine Datei) verwenden, auf die Ihr Programm zugreifen muss, wenn es ausgeführt wird, und wenn die Ressource von diesen verwendet wird Andere Programme Ihr Programm ist für die Verwendung gesperrt. In diesem Fall, wenn Sie auf die Ressource in Ihrem Code zugreifen, versetzen Sie Ihren Zugriff auf die Ressource in einen Try-Catch (um die Ausnahme abzufangen, wenn Sie nicht auf die Ressource zugreifen können) und setzen dies in eine while-Schleife. Wenn die Ressource frei ist, wird der Schlaf nie aufgerufen. Wenn die Ressource jedoch blockiert ist, schlafen Sie eine angemessene Zeit lang und versuchen erneut, auf die Ressource zuzugreifen (aus diesem Grund führen Sie eine Schleife aus). Bedenken Sie jedoch, dass Sie eine Art Begrenzer in die Schleife einfügen müssen, damit es sich nicht um eine potenziell unendliche Schleife handelt. Sie können die Begrenzungsbedingung auf N Versuche festlegen (dies wird normalerweise verwendet) oder die Systemuhr überprüfen, eine feste Zeit hinzufügen, um ein Zeitlimit zu erhalten, und den Zugriffsversuch beenden, wenn Sie das Zeitlimit erreichen.

3
Steve Greene

Es ist die 1) .spinning- und 2) .polling-Schleife Ihrer Beispiele, die die Leute vor warnen, nicht vor dem Thread.Sleep () -Teil. Ich denke, Thread.Sleep () wird normalerweise hinzugefügt, um Code, der sich dreht oder sich in einer Abfrageschleife befindet, einfach zu verbessern, sodass es nur mit "schlechtem" Code in Verbindung gebracht wird.

Außerdem machen die Leute Sachen wie:

while(inWait)Thread.Sleep(5000); 

wobei auf die Variable inWait nicht threadsicher zugegriffen wird, was ebenfalls Probleme verursacht.

Was Programmierer sehen möchten, sind die Threads, die von Events und Signaling and Locking-Konstrukten gesteuert werden, und wenn Sie dies tun, brauchen Sie Thread.Sleep () nicht, und die Bedenken hinsichtlich des thread-sicheren Variablenzugriffs werden ebenfalls beseitigt. Könnten Sie beispielsweise einen Ereignishandler erstellen, der der FileSystemWatcher-Klasse zugeordnet ist, und ein Ereignis verwenden, um Ihr zweites Beispiel auszulösen, anstatt eine Schleife auszuführen?

Wie Andreas N. bereits sagte, lesen Sie Threading in C #, von Joe Albahari , es ist wirklich sehr, sehr gut.

3
mike