Home > Backend Development > C#.Net Tutorial > Detailed explanation of the use of Timer in C# and solving reentrancy issues

Detailed explanation of the use of Timer in C# and solving reentrancy issues

黄舟
Release: 2017-03-21 11:50:02
Original
2187 people have browsed it

This article mainly introduces the relevant knowledge about using Timer in C# and solving reentrancy problems. It has a very good reference value. Let’s take a look at it with the editor.

Preface

I opened the long-lost Live Writer, and I haven’t written a blog for a long time. Really too lazy. Without further ado, let’s go directly to the topic of this blog – Timer. Why am I writing this? Because I was invited by a friend a few days ago to make a "hacker" gadget. The function is quite simple, it is to automatically obtain the contents of the clipboard and then send an email. You need to use a Timer to loop through the contents of the clipboard. , but due to the function of sending emails, the SmtpClient using C# has never been able to send emails. I have written a similar function of sending emails before. At that time, I could use NetEase's, but now I can't use it. I don't know what happened, so I had to give up. When using Timer, I encountered a problem that I had not thought of before - reentrancy.

Introduction

First of all, let’s briefly introduce timer. The timer mentioned here refers to System.Timers.timer. As the name suggests, it can be triggered at a specified interval. event. The official introduction is here, and excerpts are as follows:

The Timer component is a server-based timer that enables you to specify a periodic interval at which the Elapsed event is raised in your application. This event can then be handled to provide general processing. For example, let's say you have a critical server that must be running 24 hours a day, 7 days a week. You can create a service that uses a Timer to periodically check the server and make sure the system is up and running. If the system becomes unresponsive, the service can try to restart the server or notify the administrator. Server-based Timers are designed for use with secondary threads in multi-threaded environments. Server timers can move between threads to handle raised Elapsed events, allowing events to be raised at a more precise time than Windows timers.

If you want to know the difference from other timers, you can read here, which has a detailed introduction, so I won’t say more (actually, I didn’t know there were so many more). So what are the benefits of using this timer? Mainly because it is implemented through .NET Thread Pool, is lightweight, has accurate timing, and has no special requirements for applications and messages.

Usage

Let me briefly introduce how to use this Timer. It is actually very simple. I will use the example provided by Microsoft for testing. Go directly to 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);
 }
Copy after login

The running results are as follows, the timing is quite accurate:

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

Re-entry problem reproduction and analysis

What is re-entry Where to enter? This is a concept about multi-threaded programming: in a program, when multiple threads run at the same time, the same method may be called by multiple processes at the same time. When there is some non-thread safety code in this method, method reentrancy will lead to data inconsistency. Timer method reentrancy refers to the use of multi-threaded timers. Before the processing of one Timer is completed, when the time is up, the other Timer will continue to enter the method for processing. The following demonstrates the occurrence of the reentrancy problem (the reproduction may not be very good, but it can also briefly explain the problem):

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

The output results are shown below:

Do you feel that the above output is strange? First, thread 1 outputs 1, which is no problem. Then after 2 seconds, thread 1 increments by 1 and outputs 2. This is a problem. Why is there still a problem in the middle? The output of thread 2 appears? What’s even more strange is that the output of thread 2 was 1 at the beginning, but after incrementing by 1, it became 3! In fact, this is the problem caused by reentrancy. Don’t worry, we’ll find out the reason after some analysis.

First, after the timer starts timing, a thread 1 execution method is started. When thread 1 outputs for the first time, thread 1 sleeps for 2 seconds. At this time, the timer is not idle because of the set timing interval. is 1 second. After thread 1 sleeps for 1 second, the timer starts the execution method of thread 2. Thread 2 does not care whether thread 1 is executing or sleeping, so the output of thread 2 is also 1 at this time, because thread 1 It is still in the dormant state and has not increased by itself. Then another 1 second passed. At this time, two events occurred at the same time. Thread 1 passed the dormant state and the auto-increment output was 2. The timer also started a thread 3 at the same time. The output of thread 3 was the value 2 after thread 1 auto-incremented. After another 1 second, thread 2 passed the dormant state. The previous output was already 2, so the output after the auto-increment was 3. Another second passed... I almost fainted. This is probably what I mean. I want to express The problem is: the thread processing started by one Timer has not been completed. When the time is up, another Timer will continue to enter the method for processing.

So how to solve this problem? There are three solutions. Let’s go through them one by one to adapt to different scenarios. However, we still recommend the last one, which is safer.

Reentrancy problem solution

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)); } }
Copy after login

执行结果:

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;
 }
 }
Copy after login

执行结果:

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); 
 }
 }
Copy after login

执行结果:

总结

终于码完字了,真心不容易啊。写博客是个挺耗精力的事情,真心佩服那些大牛们笔耕不辍,致敬!在这里稍微总结一下,timer是一个使用挺简单的类,拿来即用,这里主要总结了使用timer时重入问题的解决,以前也没思考过这个问题,解决方案也挺简单,在这里列出了三种,不知道还有没有其他的方式。这里的解决方案同时也适用多线程的重入问题。

The above is the detailed content of Detailed explanation of the use of Timer in C# and solving reentrancy issues. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template