66 votes

Comparer l'utilisation de Thread.Sleep et de Timer pour une exécution différée

J'ai une méthode dont l'exécution doit être retardée pendant une durée déterminée.

Dois-je utiliser

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

Ou

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

J'avais lu quelques articles sur l'utilisation Thread.Sleep est une mauvaise conception. Mais je ne comprends pas vraiment pourquoi.

Mais pour utiliser Timer, Timer a la méthode dispose. Comme l'exécution est retardée, je ne sais pas comment disposer du Timer. Avez-vous des suggestions ?

Ou si vous avez des codes alternatifs pour l'exécution retardée sont également appréciés.

49voto

Eric Rosenberger Points 4649

Une différence est que System.Threading.Timer envoie le callback sur un thread du pool de threads, plutôt que de créer un nouveau thread à chaque fois. Si vous avez besoin que cela se produise plus d'une fois au cours de la vie de votre application, cela vous évitera de créer et de détruire un grand nombre de threads (un processus très gourmand en ressources, comme le souligne l'article auquel vous faites référence), puisque vous réutiliserez simplement les threads du pool, et si vous avez plus d'un timer en cours en même temps, cela signifie que vous aurez moins de threads en cours d'exécution en même temps (ce qui économise également des ressources considérables).

En d'autres termes, Timer va être beaucoup plus efficace. Elle peut aussi être plus précise, puisque Thread.Sleep est garanti d'attendre au MOINS aussi longtemps que la durée que vous avez spécifiée (le système d'exploitation peut le mettre en veille pendant beaucoup plus longtemps). Accordé, Timer ne sera toujours pas exactement exacte, mais l'intention est de déclencher le rappel aussi près que possible de l'heure spécifiée, alors que ce n'est PAS nécessairement l'intention de l'option Thread.Sleep .

Quant à la destruction de la Timer la fonction de rappel peut accepter un paramètre, de sorte que vous pouvez passer l'option Timer en tant que paramètre et appeler Dispose dans le callback (bien que je n'aie pas essayé ceci -- je suppose qu'il est possible que le Timer soit verrouillé pendant le callback).

Edit : Non, je suppose que vous ne pouvez pas le faire, puisque vous devez spécifier le paramètre de rappel dans le champ Timer constructeur lui-même.

Peut-être quelque chose comme ça ? (Encore une fois, je ne l'ai pas encore essayé)

class TimerState
{
    public Timer Timer;
}

...et pour lancer le minuteur :

TimerState state = new TimerState();

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

Le verrouillage doit empêcher la callback du timer d'essayer de libérer le timer avant que la fonction de verrouillage ne soit activée. Timer le champ ayant été défini.


Addendum : Comme l'a souligné le commentateur, si action() fait quelque chose avec l'interface utilisateur, alors l'utilisation d'un fichier System.Windows.Forms.Timer est probablement une meilleure solution, puisqu'elle exécutera le rappel sur le fil d'exécution de l'interface utilisateur. Cependant, si ce n'est pas le cas, et qu'il s'agit de Thread.Sleep vs. Threading.Timer , Threading.Timer est la voie à suivre.

4 votes

Il convient également de souligner la différence avec System.Windows.Forms.Timer, que je croire appelle une fonction sur le thread UI, ce qui est très important pour les applications WinForms !

2 votes

Pour les futurs lecteurs, Sleep n'est pas garanti d'être AU MOINS, il est documenté qu'il pourrait être moins.

4 votes

Par curiosité... où cela est-il documenté ?

19voto

Utiliser ThreadPool.RegisterWaitForSingleObject au lieu de la minuterie :

//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 votes

Lisez la réponse acceptée. Timer utilise déjà le pool de threads.

14 votes

Je préfère RegisterWaitForSingleObject pour la logique... La méthode est exécutée UNE SEULE fois quand le temps est écoulé... donc vous n'avez pas besoin de faire un tour pour arrêter le timer une fois que le travail est fait, ce qui n'est pas bon... donc RegisterWaitForSingleObject fait naturellement exactement ce qu'il veut, timer dont timer est mieux quand vous voulez exécuter une tâche plusieurs fois à des intervalles spécifiques....

0 votes

J'ai ajouté un exemple. Plus de détails disponibles à cette question stackoverflow.com/questions/1535262/

16voto

Shawn Points 8120

Je pense que Thread.Sleep est parfait si vous voulez vraiment mettre l'application en pause pendant une durée déterminée. Je pense que la raison pour laquelle les gens disent que c'est une mauvaise conception est que dans la plupart des situations, les gens ne veulent pas réellement que l'application se mette en pause.

Par exemple, je travaillais sur un client pop3 où le programmeur utilisait Thread.Sleep(1000) pour attendre que le socket récupère le courrier. Dans cette situation, il était préférable de connecter un gestionnaire d'événements au socket et de poursuivre l'exécution du programme après la fin du socket.

3 votes

Sauf que dans aucun des exemples du post, l'application n'est réellement en pause. L'auteur de l'article effectue la pause dans un thread séparé, sans appeler directement Thread.Sleep. Appeler Thread.Sleep directement, oui, mauvaise idée.

3voto

Froyke Points 21

Je me souviens avoir mis en place une solution similaire à celle d'Eric. Il s'agit toutefois d'une solution fonctionnelle ;)

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

2voto

StingyJack Points 10956

Le seul problème que j'ai avec System.Timer est que la plupart du temps, je l'ai vu utilisé pour de longs délais (heures, minutes) dans les services de sondage et les développeurs oublient souvent de lancer l'événement. Avant ils lancent la minuterie. Cela signifie que si je lance l'application ou le service, je dois attendre que la minuterie s'écoule (heures, minutes) avant qu'il ne s'exécute réellement.

Bien sûr, ce n'est pas un problème avec la minuterie, mais je pense qu'elle est souvent utilisée à mauvais escient parce qu'elle est trop facile à utiliser.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X