web-dev-qa-db-fra.com

Comparez en utilisant Thread.Sleep et Timer pour une exécution différée

J'ai une méthode qui devrait être retardée pendant un certain temps.

Devrais-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 des articles à propos de l'utilisation de Thread.Sleep, c'est une mauvaise conception. Mais je ne comprends pas vraiment pourquoi.

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

Ou si vous avez des codes alternatifs pour une exécution différée, appréciez également.

Une différence est que System.Threading.Timer distribue le rappel sur un thread de pool de threads, plutôt que de créer un nouveau thread à chaque fois. Si vous souhaitez que cela se produise plus d'une fois au cours de la vie de votre application, vous éviterez ainsi de créer et de détruire un tas de threads (processus qui nécessite beaucoup de ressources, comme le souligne l'article cité en référence). Il vous suffit de réutiliser des threads dans le pool. Si vous avez plusieurs minuteries en même temps, cela signifie que moins de threads s'exécutent en même temps (ce qui vous permet d'économiser des ressources considérables).

En d'autres termes, Timer va être beaucoup plus efficace. Il est également peut-être plus précis, car Thread.Sleep est garanti d’attendre AU MOINS aussi longtemps que le temps que vous spécifiez (le système d’exploitation peut le mettre en veille beaucoup plus longtemps). Certes, Timer ne sera toujours pas exact, mais le but est de déclencher le rappel le plus près possible de l'heure spécifiée, alors que ce n'est PAS nécessairement l'intention de Thread.Sleep.

En ce qui concerne la destruction du minuteur, le rappel peut accepter un paramètre. Vous pouvez donc passer le minuteur lui-même en tant que paramètre et appeler Dispose dans le rappel (bien que je n’aie pas essayé cela - je suppose qu’il est possible que le minuteur peut être verrouillé pendant le rappel).

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

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

class TimerState
{
    public Timer Timer;
}

... et pour démarrer le chronomètre:

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 le rappel du minuteur d'essayer de libérer le minuteur avant que le champ Minuteur n'ait été défini.


Addendum: Comme l'a fait remarquer le commentateur, si action () fait quelque chose avec l'interface utilisateur, l'utilisation d'un System.Windows.Forms.Timer est probablement un meilleur pari, car elle exécutera le rappel sur le thread d'interface utilisateur. Cependant, si ce n'est pas le cas et que cela revient à Thread.Sleep vs. Threading.Timer, Threading.Timer est la voie à suivre.

42
Eric Rosenberger

utilisez ThreadPool.RegisterWaitForSingleObject au lieu de 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);
16
miniscalope

Je pense que Thread.Sleep convient si vous souhaitez vraiment suspendre l'application pendant un laps de temps déterminé. Je pense que la raison pour laquelle les gens disent que leur conception est mauvaise est que, dans la plupart des situations, les utilisateurs ne souhaitent pas que l'application suspende ses activités.

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 une fois le socket terminé.

14
Shawn

Je me souviens d'avoir mis en place une solution similaire à celle d'Eric. Ceci est cependant un travail;)

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

Le seul bœuf que j'ai avec le System.Timer est que la plupart du temps, je l'ai vu utilisé pendant de longs délais (heures, minutes) dans les services de sondage et les développeurs oublient souvent de lancer l'événement Avant ils démarrent le chronomètre . Cela signifie que si je lance l'application ou le service, je dois attendre que la minuterie soit écoulée (heures, minutes) pour que celle-ci s'exécute réellement. 

Bien sûr, ce n’est pas un problème avec le minuteur, mais je pense que son utilisation est souvent mal utilisée car elle est trop facile à utiliser. 

1
StingyJack

@miniscalope Non, n'utilisez pas ThreadPool.RegisterWaitForSingleObject au lieu de timer, System.Threading.Timer mettra en file d'attente un rappel à exécuter sur un thread de pool de threads lorsque le temps écoulé ne nécessite pas de descripteur d'attente. Attendre qu'un objet attache un thread threadpool en attente de l'événement à signaler ou l'expiration du délai d'attente avant que le thread appelle le rappel.

0
Matt