C#中Timer使用及解决重入问题
★介绍
首先简单介绍一下timer,这里所说的timer是指的System.Timers.timer,顾名思义,就是可以在指定的间隔是引发事件。官方介绍在这里,摘抄如下:
12Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。然后可通过处理这个事件来提供常规处理。 例如,假设您有一台关键性服务器,必须每周 7 天、每天 24 小时都保持运行。 可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。 如果系统不响应,则该服务可以尝试重新启动服务器或通知管理员。
基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。 服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。
如果想了解跟其他的timer有啥区别,可以看这里,里面有详细的介绍,不再多说了(其实我也不知道还有这么多)。那使用这个计时器有啥好处呢?主要因为它是通过.NET Thread Pool实现的、轻量、计时精确、对应用程序及消息没有特别的要求。
★使用
下面就简单介绍一下,这个Timer是怎么使用的,其实很简单,我就采用微软提供的示例来进行测试,直接上代码了:
1234567891011121314151617181920212223242526272829//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);
}
运行的结果如下,计时蛮准确的:
12345678/*
按任意键退出程序。
触发的事件发生在: 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还会继续进入该方法进行处理。下面演示一下重入问题的产生(可能重现的不是很好,不过也能简单一下说明问题了):
123456789101112131415161718192021222324252627282930//用来造成线程同步问题的静态成员
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进入触发的方法中,发现已经被锁,会等待锁中的代码处理完在执行,代码如下:
12345678private
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)
{
1int
t = ++num; <br>
lock
(locko)
1234567<em id=
"__mceDel"
> {
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));
}
}</em>
执行结果:
2、设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃(注意这里是放弃,而不是等待哦,看看执行结果就明白啥意思了)执行,适用重入经常出现的场景。代码如下:
12345678910111213141516171819private
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用法参考这里。
123456789101112131415161718private
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);
}
}
执行结果:
1<img style=
"BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px"
title=
"image"
border=
"0"
alt=
"image"
src=
"/upload/201412/30/270111349058497.png"
width=
"640"
height=
"179"
>
★总结
终于码完字了,真心不容易啊。写博客是个挺耗精力的事情,真心佩服那些大牛们笔耕不辍,致敬!在这里稍微总结一下,timer是一个使用挺简单的类,拿来即用,这里主要总结了使用timer时重入问题的解决,以前也没思考过这个问题,解决方案也挺简单,在这里列出了三种,主要参考在这里,不知道还有没有其他的方式。这里的解决方案同时也适用多线程的重入问题。时间不早了,洗洗睡了。
C#中Timer使用及解决重入问题的更多相关文章
- [转]C#中Timer使用及解决重入问题
本文转自:http://www.cnblogs.com/hdkn235/archive/2014/12/27/4187925.html ★前言 打开久违的Live Writer,又已经好久没写博客了, ...
- (转) C#中Timer使用及解决重入(多线程同时调用callback函数)问题
原文链接: http://www.cnblogs.com/hdkn235/archive/2014/12/27/4187925.html
- 举例讲解Python中的死锁、可重入锁和互斥锁
举例讲解Python中的死锁.可重入锁和互斥锁 一.死锁 简单来说,死锁是一个资源被多次调用,而多次调用方都未能释放该资源就会造成死锁,这里结合例子说明下两种常见的死锁情况. 1.迭代死锁 该情况是一 ...
- redis分布式锁-可重入锁
redis分布式锁-可重入锁 上篇redis实现的分布式锁,有一个问题,它不可重入. 所谓不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞. 同一个 ...
- C语言之可重入函数 && 不可重入函数
可重入函数 在 实时系统的设计中,经常会出现多个任务调用同一个函数的情况.如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任 务调用这个函数的数据,从而导致不可预料 ...
- C语言可重入函数和不可重入函数
可重入函数和不可重入函数的概念 在函数中如果我们使用静态变量了,导致产生中断调用别的函数的 过程中可能还会调用这个函数,于是原来的 静态变量被在这里改变了,然后返回主体函数,用着的那个静态变量就被改变 ...
- C语言函数重入
C语言函数重入 可重入函数:可以被中断的函数,即这个函数执行时,可以中断其执行,可以由一个或多个任务并发使用,而不比担心数据错误. 不可重入函数(不安全函数) 不能运行在多任务环境下,除非能保证互斥( ...
- ZooKeeper 分布式锁 Curator 源码 01:可重入锁
前言 一般工作中常用的分布式锁,就是基于 Redis 和 ZooKeeper,前面已经介绍完了 Redisson 锁相关的源码,下面一起看看基于 ZooKeeper 的锁.也就是 Curator 这个 ...
- 关于C#中Timer定时器的重入问题解决方法(也适用于多线程)
项目中用到了定时器随着服务启动作定时任务,按指定的准点时间定时执行相关操作,但是在指定准点时间内我只想让它执行一次,要避免重入问题的发生. 首先简单介绍一下timer,这里所说的timer是指的Sys ...
随机推荐
- ibatis实现Iterate的使用
<iterate property="" /*可选, 从传入的参数集合中使用属性名去获取值, 这个必须是一个List类型, 否则会出现OutofRangeException, ...
- 转:Maven常用命令
转:Maven常用命令 Maven库: http://repo2.maven.org/maven2/ Maven依赖查询: http://mvnrepository.com/ Maven常用命令: 1 ...
- Codeforces Round #371 (Div. 2) C
传送门 map或者字典数的应用 简单题 题意: 思路: AC代码: #include<iostream> #include<cstring> #include<cmat ...
- 检测计算机已经安装的NetFramework版本(摘录)
/// /// 检测计算机已经安装的NetFramework版本 /// internal void GetVersionFromRegistry() { using (RegistryKey ndp ...
- workspace路径有中文情况会报java.net.MalformedURLException: unknown protocol: d错误
原因及描述:java读取xml文件时如果出现中文字符就会出现这类错误 解决方法: 1.将中文路径改为英文路径 2.读取file时"file:///d:/" 而不是"d ...
- 使用IntelliJ IDEA 13搭建Android集成开发环境(图文教程)
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...
- PHP基础知识之函数
定义: <?phpclass foo-----定义类{ function do_foo()---类的方法 { echo "Doing foo."; ...
- ExtJS笔记 Field
Fields are used to define what a Model is. They aren't instantiated directly - instead, when we crea ...
- 【java基础学习】网络编程
网络编程 InetAddress tcp udp
- 关于Fekit的安装与测试
一. Fekit的安装 1.cmd输入指令 $ npm install fekit -g 2.初始化方法 $ fekit init 3.创建本地服务 $ fekit server 4.创建同时改端口( ...