Un délai System.Timers.Timer s'écoule-t-il sur un thread distinct du thread 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 qu'il faut beaucoup de temps pour modifier cet objet, par exemple 10 secondes. Est-il possible que je rencontre des collisions de threads dans ce scénario?
Pour System.Timers.Timer :
Voir réponse de Brian Gideon ci-dessous
Pour System.Threading.Timer :
Documentation MSDN sur les timers
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, le chronomètre s'écoule sur un autre fil.
Ça dépend. Le System.Timers.Timer
a deux modes de fonctionnement.
Si SynchronizingObject
est défini sur une instance ISynchronizeInvoke
, l'événement Elapsed
sera exécuté sur le thread hébergeant l'objet en cours de synchronisation. Habituellement, ces instances ISynchronizeInvoke
ne sont autres que de simples anciennes instances Control
et Form
que nous connaissons tous. Ainsi, dans ce cas, l'événement Elapsed
est appelé sur le thread d'interface utilisateur et se comporte de la même manière que l'événement System.Windows.Forms.Timer
. Sinon, cela dépend vraiment de l'instance spécifique ISynchronizeInvoke
qui a été utilisée.
Si SynchronizingObject
est null, l'événement Elapsed
est appelé sur un thread ThreadPool
et il se comporte comme le System.Threading.Timer
. En fait, il utilise en fait un System.Threading.Timer
en coulisse et effectue le marshaling after il reçoit le rappel de la minuterie si nécessaire.
Chaque événement écoulé sera déclenché dans le même thread, sauf si un événement Elapsed précédent est toujours en cours d'exécution.
Donc, il gère la collision pour vous
essayez de mettre ceci 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 fil appelant et 6 et 12 déclenchent à 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
Depuis 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.
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 a tiré sur un intervalle de 5 secondes:
Main Thread Id: 9
12
12
12
12
12
...
Donc, 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 de OnDummyTimerFired n'est pas effectuée lors du prochain tick, alors .NET créerait un nouveau thread pour effectuer ce travail.
"La classe System.Timers.Timer fournit un moyen simple de gérer ce dilemme: elle expose une propriété publique SynchronizingObject. Définir cette propriété comme une instance d'un Windows Form (ou un contrôle sur un formulaire Windows) garantira que le code de votre gestionnaire d’événements Elapsed s’exécute sur le même thread que celui sur lequel SynchronizingObject a été instancié. "
Si l'événement écoulé dure plus longtemps que l'intervalle, il créera un autre thread pour déclencher l'événement écoulé. Mais il existe une solution de contournement pour cette
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
timer.Stop();
Thread.Sleep(2000);
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
finally
{
timer.Start();
}
}