Heim  >  Artikel  >  Backend-Entwicklung  >  Ausführliche Erläuterung der Verwendung von Timer in C# und der Lösung von Wiedereintrittsproblemen

Ausführliche Erläuterung der Verwendung von Timer in C# und der Lösung von Wiedereintrittsproblemen

黄舟
黄舟Original
2017-03-21 11:50:022094Durchsuche

In diesem Artikel werden hauptsächlich die relevanten Kenntnisse zur Verwendung von Timer in C# und zur Lösung von Wiedereintrittsproblemen vorgestellt. Es hat einen sehr guten Referenzwert. Schauen wir es uns mit dem Herausgeber an.

Vorwort

Ich habe den lange verlorenen Live Writer geöffnet Habe schon lange keinen Blog mehr geschrieben. Wirklich zu faul. Kommen wir ohne weitere Umschweife direkt zum Thema dieses Blogs – Timer. Warum schreibe ich das? Weil ich vor ein paar Tagen von einem Freund eingeladen wurde, ein „Hacker“-Gadget zu erstellen. Die Funktion besteht darin, den Inhalt der Zwischenablage automatisch abzurufen und dann eine E-Mail zu senden Verwenden Sie einen Timer, um den Inhalt der Zwischenablage zu durchlaufen, aber aufgrund der Funktion zum Senden von E-Mails war der SmtpClient noch nie in der Lage, E-Mails zu senden Ich könnte NetEase verwenden, aber jetzt kann ich es nicht verwenden. Ich weiß nicht, was passiert ist, also musste ich aufgeben. Bei der Verwendung von Timer bin ich auf ein Problem gestoßen, an das ich vorher nicht gedacht hatte: Wiedereintritt.

Einführung

Lassen Sie uns zunächst kurz den Timer vorstellen, der sich auf System.Timers.timer bezieht in einem bestimmten Intervall. Die offizielle Einführung finden Sie hier und Auszüge lauten wie folgt:

Die Timer-Komponente ist ein serverbasierter Timer, mit dem Sie ein periodisches Intervall angeben können, in dem das Elapsed-Ereignis in Ihrer Anwendung ausgelöst wird. Dieses Ereignis kann dann behandelt werden, um eine allgemeine Verarbeitung bereitzustellen. Nehmen wir zum Beispiel an, Sie haben einen kritischen Server, der 24 Stunden am Tag, 7 Tage die Woche laufen muss. Sie können einen Dienst erstellen, der einen Timer verwendet, um den Server regelmäßig zu überprüfen und sicherzustellen, dass das System betriebsbereit ist. Wenn das System nicht mehr reagiert, kann der Dienst versuchen, den Server neu zu starten oder den Administrator zu benachrichtigen. Serverbasierte Timer sind für die Verwendung mit sekundären Threads in Multithread-Umgebungen konzipiert. Server-Timer können zwischen Threads wechseln, um ausgelöste abgelaufene Ereignisse zu verarbeiten, sodass Ereignisse zu einem präziseren Zeitpunkt als Windows-Timer ausgelöst werden können.

Wenn Sie den Unterschied zu anderen Timern wissen möchten, können Sie hier lesen, wo es eine ausführliche Einführung gibt, deshalb werde ich nicht mehr sagen (eigentlich wusste ich nicht, dass es so viele gibt). mehr). Welche Vorteile bietet die Verwendung dieses Timers? Hauptsächlich, weil es über den .NET Thread Pool implementiert wird, leichtgewichtig ist, ein genaues Timing aufweist und keine besonderen Anforderungen an Anwendungen und Nachrichten stellt.

Verwendung

Das Folgende ist eine kurze Einführung in die Verwendung dieses Timers. Ich werde das von Microsoft bereitgestellte Beispiel zum Testen verwenden. Gehen Sie direkt zum 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);
 }

Die laufenden Ergebnisse sind wie folgt, das Timing ist ziemlich genau:

/*
按任意键退出程序。
触发的事件发生在: 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
*/

Reproduktion und Analyse von Wiedereintrittsproblemen

Was ist Wiedereintritt? Dies ist ein Konzept der Multithread-Programmierung: Wenn in einem Programm mehrere Threads gleichzeitig ausgeführt werden, kann dieselbe Methode von mehreren Prozessen gleichzeitig aufgerufen werden. Wenn diese Methode Nicht-Thread-Sicherheitscode enthält, führt der Wiedereintritt der Methode zu Dateninkonsistenzen. Der Wiedereintritt der Timer-Methode bezieht sich auf die Verwendung von Multithread-Timern. Bevor die Verarbeitung eines Timers abgeschlossen ist und die Zeit abgelaufen ist, tritt der andere Timer weiterhin in die Verarbeitungsmethode ein. Das Folgende zeigt das Auftreten des Wiedereintrittsproblems (die Reproduktion ist möglicherweise nicht sehr gut, kann das Problem aber auch kurz erklären):

//用来造成线程同步问题的静态成员
 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));
 }

Das Folgende zeigt das Ausgabeergebnis:

Finden Sie die obige Ausgabe seltsam? Zuerst ist die Ausgabe von Thread 1 1, was kein Problem darstellt. Nach 2 Sekunden erhöht sich Thread 1 um 1 und gibt 2 aus ein Problem. In der Mitte Warum erscheint die Ausgabe von Thread 2 immer noch? Noch seltsamer ist, dass die Ausgabe von Thread 2 am Anfang 1 war, nach der Erhöhung um 1 jedoch 3 wurde! Tatsächlich ist dies das Problem, das durch den Wiedereintritt verursacht wird. Machen Sie sich keine Sorgen, den Grund erfahren wir nach der Analyse.

Nachdem der Timer mit der Zeitmessung begonnen hat, wird eine Thread-1-Ausführungsmethode gestartet. Wenn Thread 1 zum ersten Mal ausgibt, schläft Thread 1 zu diesem Zeitpunkt nicht Das eingestellte Zeitintervall beträgt 1 Sekunde. Nachdem Thread 1 1 Sekunde lang schläft, öffnet der Timer die Ausführungsmethode von Thread 2. Thread 2 kümmert sich nicht darum, ob Thread 1 ausgeführt wird oder schläft, daher ist die Ausgabe von Thread 2 zu diesem Zeitpunkt ebenfalls 1 , weil Thread 1 Es befindet sich noch im Ruhezustand und hat sich nicht von selbst erhöht. Dann verging eine weitere Sekunde. Zu diesem Zeitpunkt traten zwei Ereignisse gleichzeitig in den Ruhezustand ein und der Timer startete gleichzeitig einen Thread 3 war der Wert 2, nachdem Thread 1 automatisch erhöht wurde. Nach einer weiteren Sekunde überschritt Thread 2 den Ruhezustand. Die vorherige Ausgabe war bereits 2, also war die Ausgabe nach der automatischen Erhöhung 3. Eine weitere Sekunde verstrich Das ist wahrscheinlich das, was ich sagen möchte. Das Problem ist: Die von einem Timer gestartete Thread-Verarbeitung ist noch nicht abgeschlossen. Wenn die Zeit abgelaufen ist, wird ein anderer Timer die Verarbeitung fortsetzen.

Wie kann man dieses Problem lösen? Es gibt drei Lösungen, die wir nacheinander durchgehen, um sie an verschiedene Szenarien anzupassen. Wir empfehlen jedoch immer noch die letzte Lösung, die sicherer ist.

Lösung für Wiedereintrittsprobleme

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时重入问题的解决,以前也没思考过这个问题,解决方案也挺简单,在这里列出了三种,不知道还有没有其他的方式。这里的解决方案同时也适用多线程的重入问题。

Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung der Verwendung von Timer in C# und der Lösung von Wiedereintrittsproblemen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn