>  기사  >  백엔드 개발  >  C#의 타이머 사용 및 재진입 문제 해결에 대한 자세한 설명

C#의 타이머 사용 및 재진입 문제 해결에 대한 자세한 설명

黄舟
黄舟원래의
2017-03-21 11:50:022098검색

이 글에서는 C#에서 Timer를 사용하고 재진입 문제를 해결하는 방법에 대한 관련 지식을 주로 소개합니다. 참고할만한 가치가 아주 좋습니다.

서문

오랜만에 오픈한 라이브라이터입니다. 정말 오랫동안 블로그를 쓰지 않았습니다. 더 이상 고민하지 않고 이 블로그의 주제인 타이머로 바로 넘어가겠습니다. 제가 이 글을 쓰는 이유는 며칠 전에 친구의 초대를 받아 "해커" 가젯을 만들었는데, 기능이 매우 간단해서 클립보드의 내용을 자동으로 가져온 다음 이메일을 보내야 합니다. Timer를 사용하여 클립보드의 내용을 반복합니다. 그러나 이메일 전송 기능으로 인해 C#을 사용하는 SmtpClient는 이전에 이메일 전송과 유사한 기능을 작성한 적이 없습니다. NetEase를 사용할 수 있었는데 지금은 무슨 일이 일어났는지 모르겠어서 포기해야 했습니다. Timer를 사용하다가 이전에는 생각하지 못했던 문제, 즉 재진입 문제에 직면하게 되었습니다.

소개

우선 타이머에 대해 간단히 소개하겠습니다. 여기서 언급하는 타이머는 이름에서 알 수 있듯이 System.Timers.timer를 의미합니다. 지정된 간격으로. 공식 소개는 여기에 있으며 발췌 내용은 다음과 같습니다.

Timer 구성 요소는 애플리케이션에서 Elapsed 이벤트가 발생하는 주기적인 간격을 지정할 수 있는 서버 기반 타이머입니다. 그런 다음 이 이벤트를 처리하여 일반 처리를 제공할 수 있습니다. 예를 들어, 하루 24시간, 일주일 내내 실행되어야 하는 중요한 서버가 있다고 가정해 보겠습니다. 타이머를 사용하여 주기적으로 서버를 확인하고 시스템이 실행되고 있는지 확인하는 서비스를 만들 수 있습니다. 시스템이 응답하지 않으면 서비스는 서버를 다시 시작하거나 관리자에게 알릴 수 있습니다. 서버 기반 타이머는 다중 스레드 환경에서 보조 스레드와 함께 사용하도록 설계되었습니다. 서버 타이머는 스레드 간에 이동하여 발생한 Elapsed 이벤트를 처리할 수 있으므로 Windows 타이머보다 더 정확한 시간에 이벤트가 발생할 수 있습니다.

다른 타이머와의 차이점을 알고 싶으시면 여기를 읽어보세요. 자세한 소개가 있어서 더 이상 말하지 않겠습니다(사실 이렇게 많은 타이머가 있는지 몰랐습니다) 더). 그렇다면 이 타이머를 사용하면 어떤 이점이 있을까요? 주로 .NET 스레드 풀을 통해 구현되고, 가볍고, 정확한 타이밍을 가지며, 애플리케이션과 메시지에 대한 특별한 요구 사항이 없기 때문입니다.

사용법

다음은 이 Timer의 사용 방법에 대한 간략한 소개입니다. 실제로는 Microsoft에서 제공하는 예제를 사용하여 테스트해 보겠습니다. . 코드로 바로 이동:

//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);
 }

실행 결과는 다음과 같습니다. 타이밍이 매우 정확합니다.

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

재진입 문제 재현 및 분석

재입장이란 무엇인가요? 어디로 들어가나요? 이는 다중 스레드 프로그래밍에 대한 개념입니다. 프로그램에서 여러 스레드가 동시에 실행될 때 동일한 메서드가 여러 프로세스에서 동시에 호출될 수 있습니다. 이 메소드에 스레드가 아닌 안전 코드가 있는 경우 메소드 재진입으로 인해 데이터 불일치가 발생합니다. 타이머 방법 재진입은 다중 스레드 타이머를 사용하는 것을 의미합니다. 한 타이머의 처리가 완료되기 전에 시간이 다 되면 다른 타이머가 계속해서 처리 방법에 들어갑니다. 다음은 재진입 문제의 발생을 보여줍니다(재현이 좋지 않을 수 있지만 문제를 간략하게 설명할 수도 있음).

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

다음은 출력 결과를 보여줍니다.

위 출력이 이상하다고 느끼시나요? 먼저 스레드 1의 출력은 1이므로 문제가 없습니다. 그러면 2초 후에 스레드 1이 1씩 증가하여 2를 출력합니다. . 중간에 여전히 문제가 있는 이유는 무엇입니까? 더 이상한 점은 스레드 2의 출력이 처음에는 1이었다가 1씩 증가한 후 3이 되었다는 것입니다! 사실 이것은 재진입으로 인해 발생하는 문제입니다. 걱정하지 마세요. 몇 가지 분석을 통해 이유를 알아낼 수 있습니다.

먼저 타이머가 타이밍을 시작한 후 스레드 1 실행 방법이 시작됩니다. 스레드 1이 처음으로 출력되면 스레드 1은 2초 동안 휴면 상태가 됩니다. 설정된 타이밍 간격은 1초입니다. 스레드 1이 1초 동안 휴면한 후 타이머는 스레드 2의 실행 방법을 엽니다. 스레드 2는 스레드 1이 실행 중인지 휴면 중인지 상관하지 않으므로 이때 스레드 2의 출력도 1입니다. , 왜냐하면 쓰레드 1은 아직 휴면상태에 있어서 스스로 증가하지 않았기 때문입니다. 그리고 또 1초가 지났습니다. 이때 두 개의 이벤트가 동시에 발생했습니다. 스레드 1이 휴면 상태를 통과하고 자동 증가 출력이 2가 되었습니다. 타이머도 동시에 스레드 3의 출력을 시작했습니다. 스레드 1이 자동 증가한 후 값 2였습니다. 1초가 더 지나면 스레드 2가 휴면 상태를 통과했습니다. 이전 출력은 이미 2였으므로 자동 증가 후의 출력은 3이었습니다. 또 1초가 지났습니다... 거의 기절할 뻔했습니다. 내가 표현하고 싶은 것은 아마도 하나의 Timer가 시작한 스레드 처리가 완료되지 않았는데, 시간이 다 되면 다른 Timer가 계속해서 처리 방법을 입력한다는 것입니다.

그럼 이 문제를 어떻게 해결해야 할까요? 세 가지 해결책이 있습니다. 다양한 시나리오에 적응하기 위해 하나씩 살펴보겠습니다. 그러나 우리는 여전히 더 안전한 마지막 방법을 권장합니다.

재진입 문제 해결

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

위 내용은 C#의 타이머 사용 및 재진입 문제 해결에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.