C# 多线程九之Timer类
1、简介
相信写过定时任务的小伙伴都知道这个类,非常的轻量级,而且FCL中大量的类使用了这个方法,比如CancellationTokenSource的CancelAfter就是用Timer去做的.

当然FCL中大量的使用了Timer,说明MS对Timer类是信任的.下面就开始介绍这个类的用法.简介很少,但是很有力,FCL中都用了这么多,所以我们不应该带有色眼镜看它.当然它也不是万能的,要不然就不会出现那么多的定时任务项目了.
Timer的本质:当计时器档期,CLR会将我们的回调函数放入到线程池队列中,并执行我们的回调函数.仅此而已.下面会演示
2、基本用法
使用 System.Threading.Timer前,你必须知道它是基于线程池线程的,其实,Timer的作用是定时(可以是一个时间点,可以试一段时间)调用一个方法,但是他是怎么做的呢?其实当你在你的代码中创建了一个或多个Timer实例时,线程池会给每个的Timer实例分配一个线程,代码如下:
static void Main(string[] args)
{
var timer = new Timer(state =>
{
Console.WriteLine("每秒执行一次的定时任务,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
}, null, , ); var timer2 = new Timer(state =>
{
Console.WriteLine("每秒执行一次的定时任务,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
}, null, , );
Console.ReadKey();
}

两个定时任务,分配了三个线程,很奇怪,我还以为只会给一个Timer实例分配一个线程,但事实并不是.那么证明当一个timer当期时,线程池就会唤起一个空闲的线程去执行回调函数.如果你把间隔的时间改长,如下:
static void Main(string[] args)
{
var timer = new Timer(state =>
{
Console.WriteLine("每秒执行一次的定时任务,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
}, null, , ); var timer2 = new Timer(state =>
{
Console.WriteLine("每秒执行一次的定时任务,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
}, null, , );
Console.ReadKey();
}

只会唤起两个线程.
如果把时间改的非常小,如下:
static void Main(string[] args)
{
var timer = new Timer(state =>
{
Console.WriteLine("每秒执行一次的定时任务,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
}, null, , ); var timer2 = new Timer(state =>
{
Console.WriteLine("每秒执行一次的定时任务,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
}, null, , );
Console.ReadKey();
}

回唤起更多的线程参与运算,综上所述每个回调方法线程池会给它分配一个线程,到底会分配多少个线程取决于你定的间隔时间.
3、里面的坑
(1)、线程安全问题
有了上面的实践,所以当你需要给Timer传递共享的参数时,必须要考虑线程安全问题,要不然就会像下面这样:
static void Main(string[] args)
{
var totalCount = ;
var param = ;
var timer2 = new Timer(state =>
{
//线程安全的加法操作
Interlocked.Add(ref totalCount, param++);
//不安全的操作
param = param++;
Console.WriteLine("每秒执行一次的定时任务,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
}, null, , );
Console.ReadKey();
}

so,你懂的,使用Timer要注意线程安全问题.
(2)、回调函数的执行时间大于给Timer实例设置的时间间隔
static object lockObj = new object();
static void Main(string[] args)
{
var count = ;
var timer2 = new Timer(state =>
{
lock (lockObj)
{
count++;
}
//如果线程池会等待该方法执行完毕,那么6秒后会输出2;
Console.WriteLine(count);
Thread.Sleep();
}, null,, );
Console.ReadKey();
}

事实证明不是,需要你自己去跑下上面这段代码,总之Timer并没有等待回调函数执行完毕,而是没过500毫秒唤起一个线程执行+1操作.导致了多个线程池执行了这个回调方法.
那么如何解决这个问题呢?如下:
class Program
{
private static Timer _timer;
static object lockObj = new object();
static void Main(string[] args)
{
var count = ;
//创建但并不启动计时器
_timer = new Timer(obj=> {
Console.WriteLine("开始执行的当前秒数:{0},当前线程Id:{1}", DateTime.Now.Second,Thread.CurrentThread.ManagedThreadId);
lock (lockObj)
{
count++;
}
Console.WriteLine(count);
Thread.Sleep();
//当前线程执行加1操作完毕后,让Timer在500毫秒后再次触发
_timer.Change(, Timeout.Infinite);
Console.WriteLine("执行完毕后的当前秒数:{0},当前线程Id:{1}", DateTime.Now.Second, Thread.CurrentThread.ManagedThreadId);
},null,Timeout.Infinite,Timeout.Infinite); //启动计时器
_timer.Change(, Timeout.Infinite); Console.ReadKey();
}
}

所以,当你的计算任务过于复杂你无法判断它多久才会执行完毕时,上面这种做法才是最好的做法.当Timer处理完一个回调函数之后,在回调函数内部调用Change方法,重启它,这样就保证你当前执行的计算任务只会有一个线程进行调用.而不是向(1)中的那样,注意线程池不会等待上一个计算任务计算完毕之后开启一个新的timer.
(3)、时间间隔的不准确
这里不多做介绍,应为每次线程池和执行方法本身也会消耗时间,所以他的时间间隔想想都知道不是精确的.
(4)、使用async await模型搭配Task.Delay实现定时任务
static void Main(string[] args)
{
var timer = new Timer(obj => TimingOne(), null, , );
Console.ReadKey();
} /// <summary>
/// 使用async await模型搭配Task.Delay实现定时任务
/// </summary>
static async void TimingOne()
{
Console.WriteLine("循环任务一开启,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
await Task.Delay();//开启一个守护线程,强制等待2秒后,执行后面的回调方法,也可以用Task的ContineWith实现
TimingTwo();
} static async void TimingTwo()
{
Console.WriteLine("循环任务二开启,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
await Task.Delay();
TimingThree();
} static async void TimingThree()
{
Console.WriteLine("循环任务三开启,当前线程Id:{0}", Thread.CurrentThread.ManagedThreadId);
await Task.Delay();
}

缺点不多说,你必须控制好时间,如果你的计算任务的时间不确定,不建议用这种方式,而且这里也可以使用Task.ContinueWith来实现,这里就不说了,因为async和await就是他的语法糖.
C# 多线程九之Timer类的更多相关文章
- C#的timer类
在C#里关于定时器类就有3个 1.定义在System.Windows.Forms里 2.定义在System.Threading.Timer类里 3.定义在System.Timers.Timer类里 S ...
- 给System.Timer类的Elapsed事件加锁
背景: 最近在做一个项目,程序是命令行程序,在主程序中开一个线程,这个线程用到了System.Timer类的Elapsed事件,根据指定时间间隔循环去查询数据库,找符合条件的记录,把记录组织成xml对 ...
- 适配器、工厂模式、线程池、线程组、互斥锁、Timer类、Runtime类、单例设计模式(二十四)
1.多线程方法 * Thread 里面的俩个方法* 1.yield让出CPU,又称为礼让线程* 2.setPriority()设置线程的优先级 * 优先级最大是10,Thread.MAX_PRIORI ...
- java中Timer类的详细介绍(详解)
一.概念 定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和多线程技术还是有非常大的关联的.在JDK中Timer类主要负责计划任务的功能,也就是在指定 ...
- 本地数据Store。Cookie,Session,Cache的理解。Timer类主要用于定时性、周期性任务 的触发。刷新Store,Panel
本地数据Store var monthStore = Ext.create('Ext.data.Store', { storeId : 'monthStore', autoLoad : false, ...
- C#中timer类的用法
C#中timer类的用法 关于C#中timer类 在C#里关于定时器类就有3个 1.定义在System.Windows.Forms里 2.定义在System.Threading.Timer类 ...
- Timer类和TimerTask类
Timer类是一种线程设施,可以用来实现在某一个时间或某一段时间后安排某一个任务执行一次或定期重复执行. 该功能要与TimerTask类配合使用.TimerTask类用来实现由Timer安排的一次或重 ...
- 使用Timer类的两个实例 动态时钟
package chapter16; import javax.swing.*; import chapter15.StillClock; import java.awt.event.*; publi ...
- 使用System.Timers.Timer类实现程序定时执行
使用System.Timers.Timer类实现程序定时执行 在C#里关于定时器类有3个:System.Windows.Forms.Timer类.System.Threading.Timer类和Sys ...
随机推荐
- IntelliJ IDEA 2017版 编译器使用学习笔记(九)(图文详尽版);IDE使用的有趣的插件;IDE代码统计器;Mybatis插件
一.代码统计器,按照名字搜索即可,在file===setting------plugin 使用右键项目:点击自动统计 二.json转实体类 三.自动找寻bug插件 四.Remind me工具 五.检测 ...
- java编程IO简单回顾和学习
java编程IO操作必不可少的,很久不玩IO,回顾一下,写了几个小程序,记录一下,方便查阅和学习. 1.给出一个整数数组,将其写入一个文件,再从文件中读出,并按整数大小逆序打印. package co ...
- 18) maven 项目结构:继承
Project Inheritance [ɪn'herɪt(ə)ns] https://maven.apache.org/guides/introduction/introduction-to-the ...
- 强制DataNode向NameNode上报blocks
正常情况下,什么时候上报blocks,是由NameNode通过回复心跳响应的方式触发的. 一次机房搬迁中,原机房hadoop版本为2.7.2,新机房版本为2.8.0,采用先扩容再缩容的方式搬迁.由于新 ...
- shell 中的 && 和 ||
shell 中的 && 和 || 简言之,shell 中 && --左边的命令执行成功才会执行右边的命令. || -- 左边的命令执行失败才会执行右边的命令.
- Java-动态代理技术
1.程序中的代理 为具有相同接口的目标类的各个方法,添加一些系统功能,如日志,异常处理,计算方法运行的 时间,事务管理等等,都可以交给另一个类去实现这些功能,该类称为代理类. 注意:为了让代理类共享目 ...
- Azure DevOps Server 2019 (TFS)安装教程
概述 Azure DevOps Server 2019 (之前的名称为TFS),作为微软Azure DevOps 的企业私有(on-premises)服务器,是一个为开发团队提供软件协作开发管理的服务 ...
- EF架构获取数据时报错:The ObjectContext instance has been disposed and can no longer be used for operations that require a connection. Do you want to correct the value?
场景: EF底层,获取完主表,点击按钮,添加主表,字表内容时,报以上错误 解决方案: 在EF文件的空白处右键--属性,将“应用延迟加载”,改为False
- 《基于MVC的JavaScript Web富应用开发》学习笔记
第1章 MVC和类 1. 什么是MVC? MVC是一种设计模式, 它将应用划分为3个部分: 数据(模型, Model), 展现层(视图, View) 和用户交互层(控制器, Controller). ...
- 简单的C# Socket通信实例
一.套接字(socket)概念 套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元.它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本 ...