搜索
首页后端开发C#.Net教程详解C#中Timer的使用和解决重入问题

本文主要介绍了C#中Timer使用及解决重入问题的相关知识。具有很好的参考价值,下面跟着小编一起来看下吧

前言

打开久违的Live Writer,又已经好久没写博客了,真的太懒了。废话不多说了,直接进入这次博客的主题--Timer。为什么要写这个呢,因为前几天应朋友之邀,想做个“黑客”小工具,功能挺简单就是自动获取剪贴板的内容然后发送邮件,就需要用到Timer来循环获取剪贴板的内容,但是由于到了发送邮件这个功能,使用C#的SmtpClient始终发送不了邮件,以前写过类似发邮件的功能,当时可以用网易的,现在也不能用了,不知道咋回事,只好作罢。在使用Timer中遇到了之前没有想过的问题--重入。

介绍

首先简单介绍一下timer,这里所说的timer是指的System.Timers.timer,顾名思义,就是可以在指定的间隔是引发事件。官方介绍在这里,摘抄如下:

Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。然后可通过处理这个事件来提供常规处理。 例如,假设您有一台关键性服务器,必须每周 7 天、每天 24 小时都保持运行。 可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。 如果系统不响应,则该服务可以尝试重新启动服务器或通知管理员。 基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。 服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。

如果想了解跟其他的timer有啥区别,可以看这里,里面有详细的介绍,不再多说了(其实我也不知道还有这么多)。那使用这个计时器有啥好处呢?主要因为它是通过.NET Thread Pool实现的、轻量、计时精确、对应用程序及消息没有特别的要求。

使用

下面就简单介绍一下,这个Timer是怎么使用的,其实很简单,我就采用微软提供的示例来进行测试,直接上代码了:

//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
*/

重入问题重现及分析

什么叫重入呢?这是一个有关多线程编程的概念:程序中,多个线程同时运行时,就可能发生同一个方法被多个进程同时调用的情况。当这个方法中存在一些非线程安全的代码时,方法重入会导致数据不一致的情况。Timer方法重入是指使用多线程计时器,一个Timer处理还没有完成,到了时间,另一Timer还会继续进入该方法进行处理。下面演示一下重入问题的产生(可能重现的不是很好,不过也能简单一下说明问题了):

//用来造成线程同步问题的静态成员
 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的输出?更奇怪的是线程2刚开始输出为1,自增1后尽然变成了3!其实这就是重入所导致的问题。别急,咱们分析一下就知道其中的缘由了。

首先timer启动计时后,开启一个线程1执行方法,当线程1第一次输出之后,这时线程1休眠了2秒,此时timer并没有闲着,因为设置的计时间隔为1秒,当在线程1休眠了1秒后,timer又开启了线程2执行方法,线程2才不管线程1是执行中还是休眠状态,所以此时线程2的输出也为1,因为线程1还在休眠状态,并没有自增。然后又隔了1秒,这时发生同时发生两个事件,线程1过了休眠状态自增输出为2,timer同时又开启一个线程3,线程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#中Timer的使用和解决重入问题的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
C#.NET生态系统:框架,库和工具C#.NET生态系统:框架,库和工具Apr 24, 2025 am 12:02 AM

C#.NET生态系统提供了丰富的框架和库,帮助开发者高效构建应用。1.ASP.NETCore用于构建高性能Web应用,2.EntityFrameworkCore用于数据库操作。通过理解这些工具的使用和最佳实践,开发者可以提高应用的质量和性能。

将C#.NET应用程序部署到Azure/AWS:逐步指南将C#.NET应用程序部署到Azure/AWS:逐步指南Apr 23, 2025 am 12:06 AM

如何将C#.NET应用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。1.在Azure上,使用AzureAppService和AzurePipelines自动化部署。2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda实现部署和无服务器计算。

C#.NET:强大的编程语言简介C#.NET:强大的编程语言简介Apr 22, 2025 am 12:04 AM

C#和.NET的结合为开发者提供了强大的编程环境。1)C#支持多态性和异步编程,2).NET提供跨平台能力和并发处理机制,这使得它们在桌面、Web和移动应用开发中广泛应用。

.NET框架与C#:解码术语.NET框架与C#:解码术语Apr 21, 2025 am 12:05 AM

.NETFramework是一个软件框架,C#是一种编程语言。1..NETFramework提供库和服务,支持桌面、Web和移动应用开发。2.C#设计用于.NETFramework,支持现代编程功能。3..NETFramework通过CLR管理代码执行,C#代码编译成IL后由CLR运行。4.使用.NETFramework可快速开发应用,C#提供如LINQ的高级功能。5.常见错误包括类型转换和异步编程死锁,调试需用VisualStudio工具。

揭开c#.net的神秘面纱:初学者的概述揭开c#.net的神秘面纱:初学者的概述Apr 20, 2025 am 12:11 AM

C#是一种由微软开发的现代、面向对象的编程语言,.NET是微软提供的开发框架。C#结合了C 的性能和Java的简洁性,适用于构建各种应用程序。.NET框架支持多种语言,提供垃圾回收机制,简化内存管理。

C#和.NET运行时:它们如何一起工作C#和.NET运行时:它们如何一起工作Apr 19, 2025 am 12:04 AM

C#和.NET运行时紧密合作,赋予开发者高效、强大且跨平台的开发能力。1)C#是一种类型安全且面向对象的编程语言,旨在与.NET框架无缝集成。2).NET运行时管理C#代码的执行,提供垃圾回收、类型安全等服务,确保高效和跨平台运行。

C#.NET开发:入门的初学者指南C#.NET开发:入门的初学者指南Apr 18, 2025 am 12:17 AM

要开始C#.NET开发,你需要:1.了解C#的基础知识和.NET框架的核心概念;2.掌握变量、数据类型、控制结构、函数和类的基本概念;3.学习C#的高级特性,如LINQ和异步编程;4.熟悉常见错误的调试技巧和性能优化方法。通过这些步骤,你可以逐步深入C#.NET的世界,并编写高效的应用程序。

c#和.net:了解两者之间的关系c#和.net:了解两者之间的关系Apr 17, 2025 am 12:07 AM

C#和.NET的关系是密不可分的,但它们不是一回事。C#是一门编程语言,而.NET是一个开发平台。C#用于编写代码,编译成.NET的中间语言(IL),由.NET运行时(CLR)执行。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。