使用timer定时器,防止事件重入

 

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

1
2
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;
//这个地方说明下,意思是判断inTimer是否为零,如果为零那么进入下面逻辑,然后inTimer的值变成1
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));
//把inTimer的值改为0 Interlocked.Exchange(
ref inTimer, 0); } }

 

转载于:https://www.cnblogs.com/xuxiaoshuan/p/5385843.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: timer定时器是一种计时工具,常用于需要定时执行某些任务的场景中。它能够在设定的时间到达后触发某个事件或执行某段代码。 timer定时器使用非常灵活,可以根据需求设置不同的时间间隔和执行次数。常见的定时器有单次定时器和循环定时器两种。 单次定时器可以在指定的时间间隔后执行一次任务,并且只执行一次。例如,我们可以设置一个单次定时器,在5秒后触发某个函数执行。 循环定时器可以按照设定的时间间隔不断重复执行任务。例如,我们可以设置一个循环定时器,每隔1分钟触发某个函数执行。 timer定时器不仅可以用于编程语言中,也可以用在各种电子设备中。比如,我们可以在手机上设置一个定时器,用来提醒我们在某个时间点做某件事情,或者在烹饪过程中使用定时器来控制食物的烹饪时间。 总之,timer定时器是一种非常实用的工具,可以在需要定时执行任务的场景中发挥重要作用。无论是在编程中还是在日常生活中,我们都可以利用timer定时器来提高效率和方便操作。 ### 回答2: timer定时器是一种用于控制时间间隔的设备或工具。它可以在给定的时间间隔内执行特定的任务或操作。 timer定时器通常用于各种计时和定时操作,例如在游戏中实现角色的移动、动画效果的切换,或者定时播放音乐等。 在编程中,我们可以使用timer定时器来实现事件的定时触发。通过设置一个特定的时间间隔和一个回调函数,当时间到达设定的时间间隔时,定时器将会触发回调函数,并执行其中的代码。 timer定时器可以在编程语言中以不同形式实现,例如在C语言中,我们可以使用标准库中的函数来创建和使用定时器。在C语言中,我们通常需要定义一个回调函数,用于实现定时触发时需要执行的操作,然后使用定时器函数来设置时间间隔和回调函数。 定时器使用可以帮助我们在程序中实现各种时间相关的任务,提高程序的可靠性和运行效率。但是需要注意的是,在使用定时器时需要合理设置时间间隔,避免频繁触发定时器导致系统负荷过大和性能下降。 总结起来,timer定时器是一种用于控制时间间隔的设备或工具,它在编程中可以用来实现各种时间相关的操作。通过设置时间间隔和回调函数,我们可以实现定时触发并执行相应的代码。 ### 回答3: timer定时器是一种用来测量或控制时间间隔的设备或工具。它通常用于在特定时间间隔内执行一些操作或发出信号。 timer定时器有广泛的应用领域。在家庭生活中,我们可以使用定时器来设置闹钟,提醒我们起床、上班、做饭等。在厨房中,我们可以设置烤箱定时器来保证食物在正确定时烹饪,防止过度或不足。 在工业领域,timer定时器被广泛应用于自动化系统中。它可以用来控制设备的运行时间,例如定时关闭空调或照明设备,节约能源。定时器还可以用于控制机器的运转周期,在特定时间间隔内启动或停止机器,提高生产效率。 在计算机编程中,timer定时器也是一个重要的概念。它可以用来控制代码的执行时间,例如设置一个定时器在特定时间间隔内反复执行某个函数或代码块。这在游戏开发中很常见,用来实现动画效果或定期更新游戏状态。 总之,timer定时器在我们的生活和工作中扮演着重要的角色。无论是作为一个简单的时间管理工具还是一个复杂的控制设备,它都能够帮助我们提高效率、节约时间,并实现各种不同的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值