2008-12-24 12 views
50

Ich habe eine Methode, die für eine bestimmte Zeit verzögert ausgeführt werden sollte.Vergleichen mit Thread.Sleep und Timer für verzögerte Ausführung

Sollte ich

Thread thread = new Thread(() => { 
    Thread.Sleep(millisecond); 
    action(); 
}); 
thread.IsBackground = true; 
thread.Start(); 

Oder

Timer timer = new Timer(o => action(), null, millisecond, -1); 

ich einige articles gelesen hatte mit Thread.Sleep schlechtes Design. Aber ich verstehe nicht wirklich warum.

Aber für die Verwendung von Timer hat Timer Verfügungsmethode. Da die Ausführung verzögert ist, weiß ich nicht wie Timer zu entsorgen ist. Hast du irgendwelche Vorschläge?

Oder wenn Sie alternative Codes für die verzögerte Ausführung haben, sind auch zu schätzen.

Antwort

39

Ein Unterschied besteht darin, dass System.Threading.Timer den Rückruf für einen Threadpool-Thread absetzt, anstatt jedes Mal einen neuen Thread zu erstellen. Wenn Sie dies mehr als einmal während der Laufzeit Ihrer Anwendung benötigen, sparen Sie den Overhead beim Erstellen und Zerstören einer Menge von Threads (ein Prozess, der sehr ressourcenintensiv ist, wie der Artikel, auf den Sie verweisen, hervorhebt) verwenden Sie einfach Threads im Pool, und wenn Sie mehr als einen Timer auf einmal haben, bedeutet dies, dass Sie weniger Threads auf einmal laufen haben (auch das spart erhebliche Ressourcen).

Mit anderen Worten, Timer wird viel effizienter sein. Es kann auch genauer sein, da Thread.Sleep garantiert nur so lange wartet, wie die von Ihnen angegebene Zeit (das Betriebssystem kann es viel länger schlafen legen). Zugegeben, Timer wird immer noch nicht genau sein, aber die Absicht ist es, den Callback so nahe wie möglich an der angegebenen Zeit abzufeuern, obwohl dies NICHT unbedingt die Absicht von Thread.Sleep ist.

Zum Löschen des Timers kann der Callback einen Parameter akzeptieren, so dass Sie möglicherweise den Timer selbst als Parameter übergeben und Dispose im Callback aufrufen können (obwohl ich das nicht versucht habe - ich denke, es ist möglich, dass der Timer während des Callbacks gesperrt sein könnte).

Edit: Nein, ich denke, Sie können dies nicht tun, da Sie den Callback-Parameter im Timer-Konstruktor selbst angeben müssen.

Vielleicht so etwas? (Auch hier haben nicht wirklich versucht)

class TimerState 
{ 
    public Timer Timer; 
} 

... und den Timer zu starten:

TimerState state = new TimerState(); 

lock (state) 
{ 
    state.Timer = new Timer((callbackState) => { 
     action(); 
     lock (callbackState) { callbackState.Timer.Dispose(); } 
     }, state, millisecond, -1); 
} 

Die Verriegelung sollte die Timer-Rückruf verhindern versucht, den Timer vor dem Timer frei Feld wurde festgelegt.


Nachtrag: Wie der Kommentator darauf hingewiesen, wenn action() etwas mit der Benutzeroberfläche des Fall ist, dann eine System.Windows.Forms.Timer verwendet, ist wahrscheinlich eine bessere Wette, da es den Rückruf auf der Benutzeroberfläche läuft Faden. Wenn dies jedoch nicht der Fall ist und es auf Thread.Sleep vs. Threading.Timer ankommt, ist Threading.Timer der richtige Weg.

+4

Es ist auch erwähnenswert, den Unterschied mit System.Windows.Forms.Timer, die ich glaube * eine Funktion auf dem UI-Thread aufruft, die wirklich wichtig für WinForms-Anwendungen ist! –

+2

Für zukünftige Leser ist 'Sleep' nicht garantiert, dass es MINDESTENS ist, es ist dokumentiert, dass es weniger sein könnte. –

+4

Aus Neugier ... wo ist das dokumentiert? –

13

Ich denke Thread.Sleep ist in Ordnung, wenn Sie wirklich die Anwendung für eine bestimmte Zeit pausieren möchten. Ich denke, der Grund, warum Leute sagen, dass es ein schlechtes Design ist, liegt darin, dass die Leute in den meisten Situationen nicht wirklich wollen, dass die Anwendung pausiert.

Zum Beispiel arbeitete ich an einem Pop3-Client, wo der Programmierer Thread.Sleep (1000) verwendet, um zu warten, während der Socket Mail abgerufen. In dieser Situation war es besser, einen Event-Handler an den Socket anzuschließen und die Programmausführung fortzusetzen, nachdem der Socket abgeschlossen war.

+2

Außer in keinem Beispiel in der Post ist die Anwendung tatsächlich pausiert. Das Poster macht die Pause in einem separaten Thread und ruft Thread.Sleep nicht direkt an. Aufruf Thread.Sleep direkt, ja, schlechte Idee. –

1

Das einzige Rindfleisch, das ich mit dem System.Timer habe, ist die meiste Zeit habe ich gesehen, dass es für lange Verzögerungen (Stunden, Minuten) in Polling-Diensten verwendet und Entwickler oft vergessen, das Ereignis Vor starten sie starten der Timer. Das heißt, wenn ich die App oder den Dienst starte, muss ich warten, bis der Timer abgelaufen ist (Stunden, Minuten), bevor er ausgeführt wird.

Sicher, das ist kein Problem mit dem Timer, aber ich denke, dass es oft falsch verwendet wird, weil es einfach zu leicht zu missbrauchen ist.

16

Verwendung ThreadPool.RegisterWaitForSingleObject statt Timer:

//Wait 5 seconds then print out to console. 
//You can replace AutoResetEvent with a Semaphore or EventWaitHandle if you want to execute the command on those events and/or the timeout 
System.Threading.ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (state, bTimeout) => Console.WriteLine(state), "This is my state variable", TimeSpan.FromSeconds(5), true); 
+0

Lesen Sie die akzeptierte Antwort. Timer verwendet bereits den Thread-Pool. –

+13

Ich bevorzuge RegisterWaitForSingleObject für die Logik ... Die Methode wird ONCE ausgeführt, wenn die Zeit abgelaufen ist ... so müssen Sie nicht Trick, um den Timer zu stoppen, sobald der Job fertig ist, ist das nicht gut ... so RegisterWaitForSingleObject natürlich genau das, was er will, Timer nicht Timer ist besser, wenn Sie eine Aufgabe mehrmals in bestimmten Abständen ausführen möchten .... –

+0

Ich habe ein Beispiel hinzugefügt. Weitere Details zu dieser Frage http://stackoverflow.com/questions/1535262/net-timeouts-waitforsingleobject-vs-timer –

0

@miniscalope Nein verwenden ThreadPool.RegisterWaitForSingleObject nicht statt Timer wird System.Threading.Timer einen Rückruf Warteschlange auf einem Thread-Pool-Thread ausgeführt werden, wenn die Die Zeit ist verstrichen und erfordert kein Wait-Handle. Warten auf ein einzelnes Objekt bindet einen Threadpool-Thread, der darauf wartet, dass das Ereignis signalisiert wird, oder auf das Zeitlimit, bevor der Thread den Rückruf aufruft.

+0

Haben Sie eine Dokumentation dazu? Sagen Sie im Wesentlichen, dass 'ThreadPool.RegisterWaitForSingleObject' wie' Thread.Sleep' funktioniert, indem Sie Folgendes sagen: * "Warten auf ein einzelnes Objekt bindet einen Threadpool-Thread, der darauf wartet, dass das Ereignis signalisiert wird oder der Timeout abläuft, bevor der Thread den Rückruf aufruft" *? – atconway

+0

Es verwendet Thread.Sleep nicht ... nach dem Durchgraben der Quelle bedeutet es, dass es das native RegisterWaitForSingleObject aufruft (ich sage impliziert, weil die Methode, die es aufruft, extern aber als interner Laufzeitaufruf geschmückt ist ...) Wenn diese Annahme ist richtig, Sie können davon ausgehen (aus der [docs] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms685061 (v = vs.85) .aspx)), dass es eine verwenden wird warten Sie Thread aus dem systemeigenen Threadpool, um auf das Signalisieren des Objekts zu warten, und führen Sie dann den Rückruf für einen Arbeitsthread aus. – Matt

2

Ich erinnere mich an die Implementierung einer Lösung ähnlich Eric's. Dies ist jedoch eine Arbeitsgruppe ein;)

class OneTimer 
    { 
     // Created by Roy Feintuch 2009 
     // Basically we wrap a timer object in order to send itself as a context in order to dispose it after the cb invocation finished. This solves the problem of timer being GCed because going out of context 
     public static void DoOneTime(ThreadStart cb, TimeSpan dueTime) 
     { 
      var td = new TimerDisposer(); 
      var timer = new Timer(myTdToKill => 
      { 
       try 
       { 
        cb(); 
       } 
       catch (Exception ex) 
       { 
        Trace.WriteLine(string.Format("[DoOneTime] Error occured while invoking delegate. {0}", ex), "[OneTimer]"); 
       } 
       finally 
       { 
        ((TimerDisposer)myTdToKill).InternalTimer.Dispose(); 
       } 
      }, 
         td, dueTime, TimeSpan.FromMilliseconds(-1)); 

      td.InternalTimer = timer; 
     } 
    } 

    class TimerDisposer 
    { 
     public Timer InternalTimer { get; set; } 
    }