服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#中Timer使用及解决重入问题

C#中Timer使用及解决重入问题

2021-12-27 14:30刘来来 C#

本文主要介绍了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是怎么使用的,其实很简单,我就采用微软提供的示例来进行测试,直接上代码了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//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);
 }

运行的结果如下,计时蛮准确的:

?
1
2
3
4
5
6
7
8
/*
按任意键退出程序。
触发的事件发生在: 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还会继续进入该方法进行处理。下面演示一下重入问题的产生(可能重现的不是很好,不过也能简单一下说明问题了):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//用来造成线程同步问题的静态成员
 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));
 }

下面显示一下输出结果:

C#中Timer使用及解决重入问题

是不是感觉上面输出结果很奇怪,首先是线程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进入触发的方法中,发现已经被锁,会等待锁中的代码处理完在执行,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
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)); } }

 执行结果:

C#中Timer使用及解决重入问题

 2、设置一个标志,表示一个timer处理正在执行,下一个timer发生的时候发现上一个没有执行完就放弃(注意这里是放弃,而不是等待哦,看看执行结果就明白啥意思了)执行,适用重入经常出现的场景。代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}
}

执行结果:

C#中Timer使用及解决重入问题

3、在多线程下给intimer赋值不够安全,interlocked.exchange提供了一种轻量级的线程安全的给对象赋值的方法(感觉比较高上大,也是比较推荐的一种方法),执行结果与方法2一样,也是放弃执行。interlocked.exchange用法参考这里。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
 }
 }

C#中Timer使用及解决重入问题执行结果:

总结

终于码完字了,真心不容易啊。写博客是个挺耗精力的事情,真心佩服那些大牛们笔耕不辍,致敬!在这里稍微总结一下,timer是一个使用挺简单的类,拿来即用,这里主要总结了使用timer时重入问题的解决,以前也没思考过这个问题,解决方案也挺简单,在这里列出了三种,不知道还有没有其他的方式。这里的解决方案同时也适用多线程的重入问题。

参考

在这里列出文章中没有提及的参考,感谢各位前辈们智慧的结晶!

asp.net 定时器回调方法的重入

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持服务器之家!

原文链接:http://www.cnblogs.com/dongqidongqi/p/6414108.html

延伸 · 阅读

精彩推荐
  • C#深入理解C#的数组

    深入理解C#的数组

    本篇文章主要介绍了C#的数组,数组是一种数据结构,详细的介绍了数组的声明和访问等,有兴趣的可以了解一下。...

    佳园9492021-12-10
  • C#SQLite在C#中的安装与操作技巧

    SQLite在C#中的安装与操作技巧

    SQLite,是一款轻型的数据库,用于本地的数据储存。其优点有很多,下面通过本文给大家介绍SQLite在C#中的安装与操作技巧,感兴趣的的朋友参考下吧...

    蓝曈魅11162022-01-20
  • C#C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    这篇文章主要介绍了C#设计模式之Strategy策略模式解决007大破密码危机问题,简单描述了策略模式的定义并结合加密解密算法实例分析了C#策略模式的具体使用...

    GhostRider10972022-01-21
  • C#三十分钟快速掌握C# 6.0知识点

    三十分钟快速掌握C# 6.0知识点

    这篇文章主要介绍了C# 6.0的相关知识点,文中介绍的非常详细,通过这篇文字可以让大家在三十分钟内快速的掌握C# 6.0,需要的朋友可以参考借鉴,下面来...

    雨夜潇湘8272021-12-28
  • C#如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    这篇文章主要给大家介绍了关于如何使用C#将Tensorflow训练的.pb文件用在生产环境的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴...

    bbird201811792022-03-05
  • C#C#微信公众号与订阅号接口开发示例代码

    C#微信公众号与订阅号接口开发示例代码

    这篇文章主要介绍了C#微信公众号与订阅号接口开发示例代码,结合实例形式简单分析了C#针对微信接口的调用与处理技巧,需要的朋友可以参考下...

    smartsmile20127762021-11-25
  • C#利用C#实现网络爬虫

    利用C#实现网络爬虫

    这篇文章主要介绍了利用C#实现网络爬虫,完整的介绍了C#实现网络爬虫详细过程,感兴趣的小伙伴们可以参考一下...

    C#教程网11852021-11-16
  • C#VS2012 程序打包部署图文详解

    VS2012 程序打包部署图文详解

    VS2012虽然没有集成打包工具,但它为我们提供了下载的端口,需要我们手动安装一个插件InstallShield。网上有很多第三方的打包工具,但为什么偏要使用微软...

    张信秀7712021-12-15