Les minuteries C # s'écoulent-elles sur un thread séparé?

97

Un System.Timers.Timer s'écoule-t-il sur un thread distinct de celui qui l'a créé?

Disons que j'ai une classe avec une minuterie qui se déclenche toutes les 5 secondes. Lorsque la minuterie se déclenche, dans la méthode écoulée, un objet est modifié. Disons que cela prend beaucoup de temps pour modifier cet objet, comme 10 secondes. Est-il possible que je rencontre des collisions de threads dans ce scénario?

user113164
la source
Cela pourrait entraîner des problèmes. Notez qu'en général, les threads du pool de threads ne sont pas conçus pour les processus de longue durée.
Greg D
J'étais en train de rencontrer cela, en testant avec un service Windows. Ce qui a fonctionné pour moi a été de désactiver le minuteur en tant que première instruction de l'événement OnTimer, d'effectuer mes tâches, puis d'activer le minuteur à la fin. Cela a fonctionné de manière fiable dans un environnement de production pendant un certain temps.
Steve le

Réponses:

60

Pour System.Timers.Timer :

Voir la réponse de Brian Gideon ci-dessous

Pour System.Threading.Timer :

La documentation MSDN sur les minuteries indique:

La classe System.Threading.Timer effectue des rappels sur un thread ThreadPool et n'utilise pas du tout le modèle d'événement.

Donc, en effet, la minuterie s'écoule sur un fil différent.

Joren
la source
21
C'est vrai, mais c'est une classe complètement différente. L'OP a posé des questions sur la classe System.Timers.Timer.
Brian Gideon
1
Oh, tu as raison. msdn.microsoft.com/en-us/library/system.timers.timer.aspx indique «L'événement Elapsed est déclenché sur un thread ThreadPool». Même conclusion à partir de là, je suppose.
Joren
6
Eh bien, oui, mais ce n'est pas si simple. Voyez ma réponse.
Brian Gideon
192

Ça dépend. Le System.Timers.Timera deux modes de fonctionnement.

Si SynchronizingObjectest défini sur une ISynchronizeInvokeinstance, l' Elapsedévénement s'exécutera sur le thread hébergeant l'objet de synchronisation. Habituellement, ces ISynchronizeInvokeinstances ne sont rien d'autre que des instances anciennes Controlet des Forminstances que nous connaissons tous. Donc, dans ce cas, l' Elapsedévénement est appelé sur le thread d'interface utilisateur et il se comporte de la même manière que le System.Windows.Forms.Timer. Sinon, cela dépend vraiment de l' ISynchronizeInvokeinstance spécifique qui a été utilisée.

Si SynchronizingObjectest null, l' Elapsedévénement est appelé sur un ThreadPoolthread et se comporte de la même manière que le System.Threading.Timer. En fait, il utilise en fait un System.Threading.Timerdans les coulisses et effectue l'opération de marshaling après avoir reçu le rappel du minuteur si nécessaire.

Brian Gideon
la source
4
Si vous souhaitez que le rappel du minuteur s'exécute sur un nouveau thread, devez-vous utiliser un System.Threading.Timerou System.Timers.Timer?
CJ7
1
@ cj7: L'un ou l'autre peut le faire.
Brian Gideon
et si j'ai une liste de type complexe (personne) et que je veux avoir un temps à l'intérieur de chaque personne? J'ai besoin que cela fonctionne sur le même fil (toutes les personnes), car s'il appelle la méthode à la première personne, la deuxième personne doit attendre que la première mette fin à l'événement écoulé. Puis-je faire cela?
Leandro De Mello Fagundes
4
Tout le monde ... System.Timers.Timera deux modes de fonctionnement. Il peut s'exécuter sur un thread de pool de threads attribué au hasard OU il peut s'exécuter sur n'importe quel thread hébergeant l' ISynchronizeInvokeinstance. Je ne sais pas comment rendre cela plus clair. System.Threading.Timera peu (voire rien) à voir avec la question initiale.
Brian Gideon
@LeandroDeMelloFagundes Vous ne pouvez pas utiliser lockpour ça?
Ozkan
24

Chaque événement écoulé se déclenchera dans le même thread à moins qu'un précédent Elapsed ne soit toujours en cours d'exécution.

Donc, il gère la collision pour vous

essayez de mettre ça dans une console

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

vous obtiendrez quelque chose comme ça

10
6
12
6
12

où 10 est le thread appelant et 6 et 12 sont déclenchés à partir de l'événement bg elapsed. Si vous supprimez le Thread.Sleep (2000); vous obtiendrez quelque chose comme ça

10
6
6
6
6

Puisqu'il n'y a pas de collisions.

Mais cela vous laisse toujours un problème. si vous déclenchez l'événement toutes les 5 secondes et qu'il faut 10 secondes pour le modifier, vous avez besoin d'un verrouillage pour ignorer certaines modifications.

Simon
la source
8
L'ajout d'un timer.Stop()au début de la méthode d'événement Elapsed, puis d'un timer.Start()à la fin de la méthode d'événement Elapsed empêchera l'événement Elapsed de se heurter.
Metro Schtroumpf
4
Vous n'avez pas besoin de mettre timer.Stop () il vous suffit de définir timer.AutoReset = false; puis vous créez timer.Start () après avoir traité l'événement. Je pense que c'est une meilleure façon d'éviter les collisions.
João Antunes
17

Pour System.Timers.Timer, sur un thread distinct, si SynchronizingObject n'est pas défini.

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

Sortie vous verriez si DummyTimer s'est déclenché à 5 secondes d'intervalle:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

Ainsi, comme on le voit, OnDummyTimerFired est exécuté sur le thread Workers.

Non, complication supplémentaire - Si vous réduisez l'intervalle à 10 ms,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

En effet, si l'exécution précédente d'OnDummyTimerFired n'est pas effectuée lors du déclenchement suivant, .NET créerait un nouveau thread pour effectuer ce travail.

Pour compliquer encore les choses, "La classe System.Timers.Timer fournit un moyen simple de résoudre ce dilemme: elle expose une propriété SynchronizingObject publique. La définition de cette propriété sur une instance d'un Windows Form (ou un contrôle sur un Windows Form) garantira que le code de votre gestionnaire d'événements Elapsed s'exécute sur le même thread sur lequel le SynchronizingObject a été instancié. "

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2

Swab.Jat
la source
C'est un bon exemple. Je ne savais pas jusqu'à présent et j'ai besoin de verrouiller ici et là. Parfait.
Olaru Mircea
Et si je veux que le minuteur déclenche l'événement Elapsed dans le thread principal d'une application console?
jacktric
13

Si l'événement écoulé prend plus de temps que l'intervalle, il créera un autre thread pour déclencher l'événement écoulé. Mais il existe une solution de contournement pour cela

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}
Rajan
la source
+1 Réponse simple et claire, mais cela ne fonctionnera pas toujours. Il faut plutôt utiliser la propriété lock ou SynchronizingObject de timer.
RollerCosta