wake-up-neo.net

Vergleichen Sie mit Thread.Sleep und Timer für die verzögerte Ausführung

Ich habe eine Methode, die für einen bestimmten Zeitraum verzögert werden sollte.

Sollte ich es benutzen

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

Oder

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

Ich hatte einige articles über die Verwendung von Thread.Sleep gelesen. Aber ich verstehe nicht warum.

Für die Verwendung des Timers verfügt der Timer jedoch über eine Dispositionsmethode. Da die Ausführung verzögert ist, weiß ich nicht, wie ich Timer entsorgen soll. Hast du irgendwelche Vorschläge?

Oder wenn Sie alternative Codes für die verspätete Ausführung haben, sind Sie auch dankbar.

Ein Unterschied besteht darin, dass System.Threading.Timer den Rückruf in einem Threadpool-Thread auslöst, anstatt jedes Mal einen neuen Thread zu erstellen. Wenn dies während der Laufzeit Ihrer Anwendung mehr als einmal erforderlich ist, erspart dies den Aufwand für das Erstellen und Löschen einer Reihe von Threads (ein Prozess, der, wie der Artikel, auf den Sie sich beziehen, sehr ressourcenintensiv ist), da dies der Fall ist Verwenden Sie einfach Threads im Pool, und wenn Sie mehr als einen Timer auf einmal ausführen, bedeutet dies, dass Sie weniger Threads gleichzeitig ausführen (was auch erhebliche Ressourcen einspart).

Mit anderen Worten, Timer wird viel effizienter sein. Es kann auch genauer sein, da Thread.Sleep nur garantiert MINDESTENS wartet, solange die von Ihnen angegebene Zeit (das Betriebssystem setzt möglicherweise länger in den Ruhezustand versetzt). Zugegeben, Timer ist immer noch nicht genau, aber es ist beabsichtigt, den Callback so nahe wie möglich an der angegebenen Zeit auszulösen, während dies NICHT unbedingt die Absicht von Thread.Sleep ist.

Für die Zerstörung des Timers kann der Callback einen Parameter akzeptieren, so dass Sie den Timer selbst als Parameter übergeben und Dispose im Callback aufrufen können (obwohl ich dies nicht versucht habe - ich denke, es ist möglich, dass der Timer ist möglicherweise während des Rückrufs gesperrt).

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

Vielleicht so etwas? (Nochmals nicht ausprobiert)

class TimerState
{
    public Timer Timer;
}

... und um den Timer zu starten:

TimerState state = new TimerState();

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

Das Sperren sollte verhindern, dass der Timer-Rückruf versucht, den Timer freizugeben, bevor das Timer-Feld gesetzt wurde.


Nachtrag: Wie der Kommentator darauf hinweist, wenn action () etwas mit der Benutzeroberfläche macht, dann ist die Verwendung eines System.Windows.Forms.Timer wahrscheinlich die bessere Wahl, da der Callback auf dem Benutzeroberflächenthread ausgeführt wird. Wenn dies jedoch nicht der Fall ist und nur Thread.Sleep vs. Threading.Timer ist, ist Threading.Timer der richtige Weg.

42

verwenden Sie ThreadPool.RegisterWaitForSingleObject anstelle des Timers:

//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);
16
miniscalope

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

Ich arbeitete zum Beispiel an einem pop3-Client, bei dem der Programmierer Thread.Sleep (1000) verwendete, um zu warten, während die Socket-Mail abgerufen wurde. In dieser Situation war es besser, einen Event-Handler an den Socket anzuschließen und die Programmausführung fortzusetzen, nachdem der Socket abgeschlossen wurde.

14
Shawn

Ich erinnere mich, dass ich eine Lösung ähnlich der von Eric implementiert habe. Dies ist jedoch ein funktionierender;)

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; }
    }
2
Froyke

Das einzige Rindfleisch, das ich mit dem System.Timer habe, ist, dass ich meistens gesehen habe, dass es für lange Verzögerungen (Stunden, Minuten) bei Abrufdiensten verwendet wurde und Entwickler oft vergessen, das Ereignis Before zu starten, das 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 tatsächlich 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. 

1
StingyJack

@miniscalope Nein, ThreadPool.RegisterWaitForSingleObject nicht anstelle des Zeitgebers verwenden. In einer Warteschlange wird System.Threading.Timer einen Rückruf in eine Warteschlange einreihen, der in einem Thread-Pool-Thread ausgeführt wird, wenn die Zeit abgelaufen ist und kein Wartezeiger benötigt binden Sie einen Threadpool-Thread, der darauf wartet, dass das Ereignis signalisiert wird oder das Timeout abläuft, bevor der Thread den Rückruf aufruft.

0
Matt