Maison > Article > développement back-end > Explication détaillée de l'utilisation de Timer en C# et résolution des problèmes de réentrance
Cet article présente principalement les connaissances pertinentes sur l'utilisation de Timer en C# et la résolution des problèmes de réentrance. Il a une très bonne valeur de référence. Jetons-y un coup d'œil avec l'éditeur
★Avant-propos
J'ai ouvert le Live Writer perdu depuis longtemps. Je n'ai pas écrit de blog depuis longtemps. Vraiment trop paresseux. Sans plus attendre, passons directement au sujet de ce blog – Timer. Pourquoi j'écris ceci ? Parce que j'ai été invité par un ami il y a quelques jours à créer un gadget "hacker". La fonction est assez simple, il s'agit d'obtenir automatiquement le contenu du presse-papiers puis d'envoyer un email. utilisez une minuterie pour parcourir le contenu du presse-papiers, mais en raison de la fonction d'envoi d'e-mails, le SmtpClient utilisant C# n'a jamais été en mesure d'envoyer des e-mails. J'avais déjà écrit une fonction similaire d'envoi d'e-mails. Je pouvais utiliser NetEase, mais maintenant je ne peux plus l'utiliser. Je ne sais pas ce qui s'est passé, alors j'ai dû abandonner. Lors de l'utilisation de Timer, j'ai rencontré un problème auquel je n'avais pas pensé auparavant : la réentrée.
★Introduction
Tout d'abord, présentons brièvement la minuterie La minuterie mentionnée ici fait référence à System.Timers.timer. Comme son nom l'indique, elle peut être déclenchée. à un événement à intervalle spécifié. L'introduction officielle est ici, et les extraits sont les suivants :
Le composant Timer est un minuteur basé sur le serveur qui vous permet de spécifier un intervalle périodique auquel l'événement Elapsed est déclenché dans votre application. Cet événement peut ensuite être géré pour fournir un traitement général. Par exemple, disons que vous disposez d'un serveur critique qui doit fonctionner 24 heures sur 24, 7 jours sur 7. Vous pouvez créer un service qui utilise une minuterie pour vérifier périodiquement le serveur et vous assurer que le système est opérationnel. Si le système ne répond plus, le service peut essayer de redémarrer le serveur ou d'en informer l'administrateur. Les minuteurs basés sur serveur sont conçus pour être utilisés avec des threads secondaires dans des environnements multithread. Les minuteurs du serveur peuvent se déplacer entre les threads pour gérer les événements écoulés déclenchés, ce qui permet aux événements d'être déclenchés à un moment plus précis que les minuteurs Windows.
Si vous voulez connaître la différence avec les autres timers, vous pouvez lire ici, qui a une introduction détaillée, donc je n'en dirai pas plus (en fait, je ne savais pas qu'il y en avait autant plus). Alors, quels sont les avantages d’utiliser cette minuterie ? Principalement parce qu'il est implémenté via le pool de threads .NET, qu'il est léger, qu'il a un timing précis et qu'il n'a aucune exigence particulière pour les applications et les messages.
★Utilisation
Ce qui suit est une brève introduction à la façon d'utiliser cette minuterie. C'est en fait très simple. Je vais utiliser l'exemple fourni par Microsoft pour le tester. . Allez directement dans le Code :
//Timer不要声明成局部变量,否则会被GC回收 private static System.Timers.Timer aTimer; public static void Main() { //实例化Timer类,设置间隔时间为10000毫秒; aTimer = new System.Timers.Timer(10000); //注册计时器的事件 aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent); //设置时间间隔为2秒(2000毫秒),覆盖构造函数设置的间隔 aTimer.Interval = 2000; //设置是执行一次(false)还是一直执行(true),默认为true aTimer.AutoReset = true; //开始计时 aTimer.Enabled = true; Console.WriteLine("按任意键退出程序。"); Console.ReadLine(); } //指定Timer触发的事件 private static void OnTimedEvent(object source, ElapsedEventArgs e) { Console.WriteLine("触发的事件发生在: {0}", e.SignalTime); }
Les résultats d'exécution sont les suivants, le timing est assez précis :
/* 按任意键退出程序。 触发的事件发生在: 2014/12/26 星期五 23:08:51 触发的事件发生在: 2014/12/26 星期五 23:08:53 触发的事件发生在: 2014/12/26 星期五 23:08:55 触发的事件发生在: 2014/12/26 星期五 23:08:57 触发的事件发生在: 2014/12/26 星期五 23:08:59 */
★Reproduction et analyse du problème de rentrée
Qu'est-ce que la réentrée ? Il s'agit d'un concept de programmation multithread : dans un programme, lorsque plusieurs threads s'exécutent en même temps, la même méthode peut être appelée par plusieurs processus en même temps. Lorsqu'il y a du code de sécurité non-thread dans cette méthode, la réentrance de la méthode entraînera une incohérence des données. La réentrance de la méthode du minuteur fait référence à l'utilisation de minuteurs multithreads avant que le traitement d'un minuteur ne soit terminé, lorsque le temps est écoulé, l'autre minuteur continuera à entrer dans la méthode pour le traitement. Ce qui suit démontre l'apparition du problème de réentrance (la reproduction n'est peut-être pas très bonne, mais elle peut aussi expliquer brièvement le problème) :
//用来造成线程同步问题的静态成员 private static int outPut = 1; //次数,timer没调一次方法自增1 private static int num = 0; private static System.Timers.Timer timer = new System.Timers.Timer(); public static void Main() { timer.Interval = 1000; timer.Elapsed += TimersTimerHandler; timer.Start(); Console.WriteLine("按任意键退出程序。"); Console.ReadLine(); } /// <summary> /// System.Timers.Timer的回调方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private static void TimersTimerHandler(object sender, EventArgs args) { int t = ++num; Console.WriteLine(string.Format("线程{0}输出:{1}, 输出时间:{2}", t, outPut.ToString(),DateTime.Now)); System.Threading.Thread.Sleep(2000); outPut++; Console.WriteLine(string.Format("线程{0}自增1后输出:{1},输出时间:{2}", t, outPut.ToString(),DateTime.Now)); }
Ce qui suit montre le résultat de sortie :
Pensez-vous que la sortie ci-dessus est étrange ? Tout d'abord, la sortie du thread 1 est 1, ce qui ne pose pas de problème. Ensuite, après 2 secondes, le thread 1 incrémente de 1 et génère 2, ce qui est. un problème. Au milieu Pourquoi la sortie du thread 2 apparaît-elle toujours ? Ce qui est encore plus étrange, c'est que la sortie du thread 2 était 1 au début, mais après avoir incrémenté de 1, elle est devenue 3 ! En fait, c’est le problème causé par la réentrée. Ne vous inquiétez pas, nous découvrirons la raison après quelques analyses.
Tout d'abord, une fois que le minuteur a démarré, une méthode d'exécution du thread 1 est démarrée. Lorsque le thread 1 sort pour la première fois, le thread 1 dort pendant 2 secondes. À ce moment, le minuteur n'est pas inactif à cause du. L'intervalle de synchronisation défini est de 1 seconde. Après que le thread 1 soit en veille pendant 1 seconde, le minuteur ouvre la méthode d'exécution du thread 2. Le thread 2 ne se soucie pas de savoir si le thread 1 est en cours d'exécution ou en veille, donc la sortie du thread 2 est également 1 à ce moment. , car thread 1 Il est toujours à l'état dormant et n'a pas augmenté de lui-même. Puis une autre seconde s'est écoulée. À ce moment-là, deux événements se sont produits en même temps. Le thread 1 est passé à l'état dormant et la sortie d'auto-incrémentation était de 2. Le minuteur a également démarré un thread 3 en même temps. était la valeur 2 après l'auto-incrémentation du thread 1. Après 1 seconde supplémentaire, le thread 2 est passé à l'état dormant. La sortie précédente était déjà 2, donc la sortie après l'auto-incrémentation était de 3. Une autre seconde s'est écoulée... J'ai failli m'évanouir. . C'est probablement ce que je veux dire. Le problème est que le traitement du thread démarré par un Timer n'est pas terminé. Une fois le temps écoulé, un autre Timer continuera à entrer dans la méthode de traitement.
Alors comment résoudre ce problème ? Il existe trois solutions. Passons-les une par une pour nous adapter aux différents scénarios. Cependant, nous recommandons toujours la dernière, qui est plus sûre.
★Solution problème de réentrée
1、使用lock(Object)的方法来防止重入,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就等待执行,适用重入很少出现的场景(具体也没研究过,可能比较占内存吧)。
代码跟上面差不多,在触发的方法中加入lock,这样当线程2进入触发的方法中,发现已经被锁,会等待锁中的代码处理完在执行,代码如下:
private static object locko = new object(); /// <summary> /// System.Timers.Timer的回调方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private static void TimersTimerHandler(object sender, EventArgs args) { int t = ++num; lock (locko) { Console.WriteLine(string.Format("线程{0}输出:{1}, 输出时间:{2}", t, outPut.ToString(), DateTime.Now)); System.Threading.Thread.Sleep(2000); outPut++; Console.WriteLine(string.Format("线程{0}自增1后输出:{1},输出时间:{2}", t, outPut.ToString(), DateTime.Now)); } }
执行结果:
2、设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃(注意这里是放弃,而不是等待哦,看看执行结果就明白啥意思了)执行,适用重入经常出现的场景。代码如下:
private static int inTimer = 0; /// <summary> /// System.Timers.Timer的回调方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private static void TimersTimerHandler(object sender, EventArgs args) { int t = ++num; if (inTimer == 0) { inTimer = 1; Console.WriteLine(string.Format("线程{0}输出:{1}, 输出时间:{2}", t, outPut.ToString(), DateTime.Now)); System.Threading.Thread.Sleep(2000); outPut++; Console.WriteLine(string.Format("线程{0}自增1后输出:{1},输出时间:{2}", t, outPut.ToString(), DateTime.Now)); inTimer = 0; } }
执行结果:
3、在多线程下给inTimer赋值不够安全,Interlocked.Exchange提供了一种轻量级的线程安全的给对象赋值的方法(感觉比较高上大,也是比较推荐的一种方法),执行结果与方法2一样,也是放弃执行。Interlocked.Exchange用法参考这里。
private static int inTimer = 0; /// <summary> /// System.Timers.Timer的回调方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private static void TimersTimerHandler(object sender, EventArgs args) { int t = ++num; if (Interlocked.Exchange(ref inTimer, 1) == 0) { Console.WriteLine(string.Format("线程{0}输出:{1}, 输出时间:{2}", t, outPut.ToString(), DateTime.Now)); System.Threading.Thread.Sleep(2000); outPut++; Console.WriteLine(string.Format("线程{0}自增1后输出:{1},输出时间:{2}", t, outPut.ToString(), DateTime.Now)); Interlocked.Exchange(ref inTimer, 0); } }
执行结果:
★总结
终于码完字了,真心不容易啊。写博客是个挺耗精力的事情,真心佩服那些大牛们笔耕不辍,致敬!在这里稍微总结一下,timer是一个使用挺简单的类,拿来即用,这里主要总结了使用timer时重入问题的解决,以前也没思考过这个问题,解决方案也挺简单,在这里列出了三种,不知道还有没有其他的方式。这里的解决方案同时也适用多线程的重入问题。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!