In C #, use Timer and solve the re-entry problem,

Source: Internet
Author: User
Tags net thread

In C #, use Timer and solve the re-entry problem,
★Preface

It's really too lazy to open the long-overdue Live Writer and haven't written a blog for a long time. If you don't talk much about it, go to Timer, the topic of this blog. Why do you want to write this? A few days ago, at the invitation of a friend, I wanted to be a hacker. The simple function was to automatically obtain the content of the clipboard and then send an email, timer is used to obtain the content of the clipboard cyclically. However, due to the function of sending emails, the SmtpClient of C # cannot send emails at all. It has previously been used to send similar emails, netease can be used at that time, and it cannot be used now. I don't know what's going on, so I have to leave it alone. I encountered a problem that I never thought about before when using Timer-re-import.

★Introduction

Timer refers to System. Timers. timer, as its name implies, that is, events can be triggered at specified intervals. The official introduction here is excerpted as follows:

The Timer component is a server-based Timer that enables you to specify the periodic interval at which Elapsed events are triggered in an application. You can then process this event to provide regular processing. For example, assume that you have a critical server that must be running 24 hours a day, 7 days a week. You can create a service that uses Timer to regularly check the server and ensure that the system is enabled and running. If the system does not respond, the service can restart the server or notify the administrator. Server-based Timer is designed for auxiliary threads in multi-threaded environments. The server timer can be moved between threads to process the triggered Elapsed event, so that the event can be triggered more accurately than the Windows timer.

If you want to know what the difference is from other timer, you can refer to the detailed introduction here, and I will not talk about it more (in fact, I don't know so much about it ). So what are the advantages of using this timer? This is mainly because it is implemented through the. NET Thread Pool, lightweight, accurate timing, and has no special requirements on applications and messages.

★Use

The following is a brief introduction of how this Timer is used. In fact, it is very simple. I will use the example provided by Microsoft for testing and directly go to the Code:

// Do not declare Timer as a local variable; otherwise, private static System will be recycled by GC. timers. timer aTimer; public static void Main () {// instantiate the Timer class, set the interval to 10000 milliseconds; aTimer = new System. timers. timer (10000); // registers the Timer event aTimer. elapsed + = new ElapsedEventHandler (OnTimedEvent); // set the interval to 2 seconds (2000 ms), covering the interval set by the constructor aTimer. interval = 2000; // set whether to execute once (false) or always execute (true). The default value is true aTimer. autoReset = true; // start timing aTimer. enabled = true; Console. writeLine (" Press any key to exit the program. "); Console. readLine ();} // specify the private static void OnTimedEvent (object source, ElapsedEventArgs e) triggered by Timer {Console. writeLine ("triggered events occur at: {0}", e. signalTime );}

The running result is as follows, and the timing is quite accurate:

/* Press any key to exit the program. The event triggered at 23:08:51 on Friday, occurred at 23:08:53 on Friday,. The event triggered at 23:08:55 on Friday, occurred at: On Friday: the event triggered at 23:08:57 on Friday occurred at: 23:08:59 on Friday */
★Reentrant problem reproduction and Analysis

What is re-import? This is a concept related to multi-threaded programming: when multiple threads run simultaneously in a program, the same method may be called by multiple processes at the same time. When some non-thread-safe code exists in this method, method re-entry may result in data inconsistency. Timer method re-import refers to the use of multi-thread Timer, a Timer processing has not been completed, by the time, the other Timer will continue to enter this method for processing. The following demonstrates the generation of the re-import problem (it may not be very good to reproduce, but it can also be explained briefly ):

// The static member private static int outPut that causes thread synchronization problems = 1; // number of times, timer does not call the method once to increase by 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 ("press any key to exit the program. "); Console. readLine () ;}/// <summary> // System. timers. timer callback method /// </summary> /// <param name = "sender"> </param> /// <param name = "args"> </param> private static void TimersTimerHandler (object sender, eventArgs args) {int t = ++ num; Console. writeLine (string. format ("thread {0} outPut: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now); System. threading. thread. sleep (2000); outPut ++; Console. writeLine (string. format ("outPut after thread {0} auto-increment 1: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now ));}

The output result is shown below:

Do you think the output result above is very strange? First, thread 1 output is 1, no problem. After 2 seconds, thread 1 increases from 1 and the output is 2, this is a problem. Why is thread 2 output in the middle? What's more strange is that the output of thread 2 is 1 at the beginning, and the output is 3 after the auto-increment is 1! In fact, this is the problem caused by re-import. Don't worry. Let's analyze it to find out why.

After timer starts timing, it starts a thread 1 execution method. When thread 1 is output for the first time, thread 1 sleep for 2 seconds. At this time, timer is not idle, because the set time interval is 1 second, when thread 1 is sleep for 1 second, timer starts thread 2 execution method again, thread 2 does not care whether thread 1 is in the running or sleep state, so the output of thread 2 is also 1 at this time, because thread 1 is still in the sleep state, and there is no auto-increment. Then, after one second, two events occur simultaneously. Thread 1 goes through the sleep state and the auto-increment output is 2. timer starts another thread 3 at the same time, thread 3 outputs the value 2 after thread 1 auto-increment, and after another 1 second, thread 2 goes through sleep, and the previous output is already 2, so after auto-increment, the output is 3, and it takes another 1 second ...... I'm almost dizzy. This is probably the meaning. What I want to express is that the processing of a thread started by Timer has not been completed yet. By the time, another Timer will continue to process this method.

How can this problem be solved? There are three solutions. We recommend the last one to adapt to different scenarios, which is safer.

★Reentrant Solution

1. Use the lock (Object) method to prevent re-entry. This indicates that a Timer processing is in progress. When the next Timer occurs, it is found that the previous one is waiting for execution, applicable to scenarios with rare re-entry operations (I have not studied it, but it may occupy memory ).

The code is similar to the above. Add lock to the trigger method. In this way, when thread 2 enters the trigger method, it finds that it has been locked and will wait for the code in the lock to be processed and executed, the Code is as follows:

Private static object locko = new object (); // <summary> // System. timers. timer callback method /// </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 ("thread {0} outPut: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now); System. threading. thread. sleep (2000); outPut ++; Console. writeLine (string. format ("outPut after thread {0} auto-increment 1: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now ));}}

Execution result:

2. Set a flag to indicate that the processing of a Timer is in progress. When the next Timer occurs, it will give up when it finds that the previous one is not finished. (Note that this is to give up, rather than waiting, see the execution results to understand what it means.) The execution is applicable to frequent re-entry scenarios. The Code is as follows:

Private static int inTimer = 0; // <summary> // System. timers. timer callback method /// </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 ("thread {0} outPut: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now); System. threading. thread. sleep (2000); outPut ++; Console. writeLine (string. format ("outPut after thread {0} auto-increment 1: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now); inTimer = 0 ;}}

Execution result:

3. It is not safe to assign a value to inTimer under multiple threads, Interlocked. exchange provides a lightweight thread-safe method for assigning values to objects (it feels high and recommended). The execution result is the same as method 2, the execution is also abandoned. For Interlocked. Exchange usage, refer to here.

Private static int inTimer = 0; // <summary> // System. timers. timer callback method /// </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 ("thread {0} outPut: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now); System. threading. thread. sleep (2000); outPut ++; Console. writeLine (string. format ("outPut after thread {0} auto-increment 1: {1}, outPut Time: {2}", t, outPut. toString (), DateTime. now); Interlocked. exchange (ref inTimer, 0 );}}

Execution result:

It's really hard to complete the code. Writing a blog is a very energy-consuming task. I really admire the experts who are not doing anything! Here I will give a brief summary. timer is a simple class to use. Here I mainly summarize the solution to the re-entry problem when timer is used. I have never thought about this problem before, the solution is also quite simple. Three methods are listed here. For more information, see here. The solution here also applies to the multi-thread reconnection problem. It's not too early. I have to go to bed.

★Reference

I would like to list the references not mentioned in this Article. Thank you for the wisdom of your predecessors!

ASP. NET ---- re-import of the timer callback Method

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.