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类的更多相关文章

  1. C#的timer类

    在C#里关于定时器类就有3个 1.定义在System.Windows.Forms里 2.定义在System.Threading.Timer类里 3.定义在System.Timers.Timer类里 S ...

  2. 给System.Timer类的Elapsed事件加锁

    背景: 最近在做一个项目,程序是命令行程序,在主程序中开一个线程,这个线程用到了System.Timer类的Elapsed事件,根据指定时间间隔循环去查询数据库,找符合条件的记录,把记录组织成xml对 ...

  3. 适配器、工厂模式、线程池、线程组、互斥锁、Timer类、Runtime类、单例设计模式(二十四)

    1.多线程方法 * Thread 里面的俩个方法* 1.yield让出CPU,又称为礼让线程* 2.setPriority()设置线程的优先级 * 优先级最大是10,Thread.MAX_PRIORI ...

  4. java中Timer类的详细介绍(详解)

    一.概念 定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和多线程技术还是有非常大的关联的.在JDK中Timer类主要负责计划任务的功能,也就是在指定 ...

  5. 本地数据Store。Cookie,Session,Cache的理解。Timer类主要用于定时性、周期性任务 的触发。刷新Store,Panel

    本地数据Store var monthStore = Ext.create('Ext.data.Store', { storeId : 'monthStore', autoLoad : false, ...

  6. C#中timer类的用法

    C#中timer类的用法 关于C#中timer类  在C#里关于定时器类就有3个   1.定义在System.Windows.Forms里   2.定义在System.Threading.Timer类 ...

  7. Timer类和TimerTask类

    Timer类是一种线程设施,可以用来实现在某一个时间或某一段时间后安排某一个任务执行一次或定期重复执行. 该功能要与TimerTask类配合使用.TimerTask类用来实现由Timer安排的一次或重 ...

  8. 使用Timer类的两个实例 动态时钟

    package chapter16; import javax.swing.*; import chapter15.StillClock; import java.awt.event.*; publi ...

  9. 使用System.Timers.Timer类实现程序定时执行

    使用System.Timers.Timer类实现程序定时执行 在C#里关于定时器类有3个:System.Windows.Forms.Timer类.System.Threading.Timer类和Sys ...

随机推荐

  1. 学以致用十三-----Centos7.2+python3+YouCompleteMe成功历程

    历经几天的摸索,趟过几趟坑之后,终于完成YouCompleteMe的安装配置. 今天同样是个不能忘记的日子,国耻日,勿忘国耻.(9.18) 服务器安装好,基本配置配置好后,开始安装. ======== ...

  2. Jersey RESTful WebService框架学习(一)

    介绍:RESTful (Representation State Transfer) 描述了一个架构样式的网络系统,比如 web 应用程序.它首次出现在 2000 年 Roy Fielding 的博士 ...

  3. LoadIcon

    1.LoadIcon(HINSTANCE hInstance,LPCSTR lpIconName);该函数从与 hInstance 模块相关联的可执行文件中装入lpIconName指定的图标资源,仅当 ...

  4. Write Markdown Syntax Online Document with Sphinx and Pandoc

    There is no doubt that we have to write doc while we are developing software. But How do you write d ...

  5. Forward团队-爬虫豆瓣top250项目-需求分析

    一. 需求:1.爬取豆瓣电影top250. 2.获取电影名称,排名,分数,简介,导演,演员. 3.将爬取到的数据保存,以便随时查看. 3.可以将获取到的数据展示给用户. 二. 参考: 豆瓣api参考资 ...

  6. spoj COT2(树上莫队)

    模板.树上莫队的分块就是按dfn分,然后区间之间转移时注意一下就好.有个图方便理解http://blog.csdn.net/thy_asdf/article/details/47377709: #in ...

  7. day31(正则表达式)

    1.校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间.^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$ 2. 校验中文字符串 ...

  8. linux下如何编写shell脚本

    我对shell脚本的认识,除了执行过同事写的shell 脚本外,其他一无所知,为了让自己强大,我决定自己研究shell脚本,也许在你看来很简答,没必要说这么多废话,但是我希望在我的技术log里记录下来 ...

  9. Codeforces Round #264 (Div. 2) D. Gargari and Permutations 多序列LIS+dp好题

    http://codeforces.com/contest/463/problem/D 求k个序列的最长公共子序列. k<=5 肯定 不能直接LCS 网上题解全是图论解法...我就来个dp的解法 ...

  10. MVVM 简化的Messager类

    看MVVMLight的Messager源码,自己实现了一个简单的Messager类. Messager类可以在MVVM中,实现View与VM.VM与VM.View与View的通信. public cl ...