.NET中至少有6种定时器,每一种定时器都有它的用途和特点。根据定时器的应用场景,可以分为UI相关的定时器和UI无关的定时器。本文将简单介绍这6种定时器的基本用法和特点。

UI定时器

.NET中的UI定时器主要是WinForm、WPF以及WebForm中的定时器。分别为:

  • System.Windows.Forms.Timer
  • System.Windows.Threading.DispatcherTimer
  • System.Web.UI.Timer

通常情况下,WinForm、WPF中的定时器是在UI线程上执行回调函数,因此可以直接访问UI元素。由于WinForm、WPF支持单线程单元模型(Single-Thread Apartment,STA),定时器间隔事件是在UI线程上触发,因此,不用担心线程安全问题。

System.Web.UI.Timer是通过Javascript定时器和服务端异步回调实现,也是单线程的。

请注意,这里说的是通常情况,后边介绍System.Windows.Threading.DispatcherTimer时会提到在非UI线程创建DispatcherTimer时也无法直接访问UI元素。

System.Windows.Forms.Timer

System.Windows.Forms.Timer针对WinForm应用进行了优化,是只能在WinForm上使用的定时器。这个定时器是针对单线程环境设计的,是在UI线程上处理定时任务。

它要求用户代码有可用的UI消息泵,定时任务须在UI线程上运行,或者跨线程通过Invoke或者BeginInvoke封送(marshal)到UI线程上运行。其优点是使用简单,只需通过给Interval属性赋值来设置时间间隔,并注册Tick事件处理定时任务。其缺点是精度不高,精度为55毫秒,也就是Interval赋值小于55时,也是55毫秒触发一次定时任务。

public partial class TimerFrom : Form
{
private System.Windows.Forms.Timer digitalClock;
private void TimerFrom_Load(object sender, EventArgs e)
{
digitalClock = new System.Windows.Forms.Timer();//创建定时器
digitalClock.Tick += new EventHandler(HandleTime);//注册定时任务事件
digitalClock.Interval = 1000;//设置时间间隔
digitalClock.Enabled = true;
digitalClock.Start(); //开启定时器
}
public void HandleTime(Object myObject, EventArgs myEventArgs)
{
labelClock.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
private void frmTimerDemo_FormClosed(object sender, FormClosedEventArgs e)
{
digitalClock.Stop();//停止定时器
digitalClock.Dispose();
}
}

System.Windows.Threading.DispatcherTimer

System.Windows.Threading.DispatcherTimer是WPF中的定时器,它是基于Dispatcher对象的(并不是基于UI线程的)。DispatcherTimer的定时任务是像其他操作一样放在Dispatcher队列上,其执行操作时间依赖于队列中其他任务及其优先级,因此,DispatcherTimer不保证在时间间隔发生时准确执行,只保证不会在时间间隔发生前执行。

Dispatcher为特定线程维护工作项(操作)的优先级队列,在线程上创建Dispatcher对象时,它成为唯一可以关联该线程的Dispatcher对象,WPF中, DispatcherObject只能被与之关联的Dispatcher对象访问,也就是非UI线程中无法直接访问UI元素(WPF中的UI元素都是派生自 DispatcherObject

此外,DispatcherTimer不像System.Windows.Forms.Timer那样只在UI线程上创建才能触发Tick事件,它在非UI线程下创建也可以触发Tick事件,此时访问UI元素也需要通过Invoke或者BeginInvoke封送(marshal)到UI线程上运行。其优点也是简单易用,适合在UI线程上执行任务或触发事件,缺点是精度不准确,可能存在延迟。

private void Dt_Tick(object sender, EventArgs e)
{
Dispatcher.BeginInvoke((Action)delegate ()
{
text1.Text = DateTime.Now.ToString();
});
Console.WriteLine(DateTime.Now.ToString());
} private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>{
DispatcherTimer dt = new DispatcherTimer();
dt.Tick += Dt_Tick;
dt.Interval = TimeSpan.FromSeconds(1);
dt.Start();
Dispatcher.Run();
});
}

上述代码中,DispatcherTimer是非UI线程中创建,定时任务中访问UI元素text1,需要通过Invoke或者BeginInvoke封送(marshal)到UI线程上运行,而Console.WriteLine则可以直接运行。

System.Web.UI.Timer

System.Web.UI.Timer是仅适用于.NET FrameworkASP.NET组件。通过Javascript定时器和服务端异步回调实现。每次触发定时器时,只能执行一个异步回调方法,而其他的异步回调方法需要等待前一个异步回调方法执行完毕后才能执行。这样可以保证在任意时刻只有一个异步回调方法在执行,避免了多线程并发执行的问题。

UI无关定时器

从 .NET 6开始,UI无关定时器有三个:

  • System.Threading.Timer
  • System.Timers.Timer
  • System.Threading.PeriodicTimer(.NET 6+)

System.Threading.Timer

System.Threading.Timer是最基础轻量的定时器,它将定期在线程池线程上执行单个回调方法。在创建定时器对象时必须指定回调方法,并且后续不能修改,同时也可以指定定时器回调开始执行的时间以及时间间隔。定时器创建后可以通过Change方法修改回调开始执行的时间以及时间间隔。该定时器的优点是轻量,精度相对较高,与Windows操作系统时钟精度一致,大约15毫秒。但因为是基于线程池的,所以在任务执行时间较长或者线程池过载时,会出现延迟。其缺点是使用不太方便,定时器创建后无法修改回调方法。

var stateTimer = new
var autoEvent = new AutoResetEvent(false);
Timer(CheckStatus, autoEvent, 1000,250); private int invokeCount=0; public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine("{0} Checking status {1,2}.",DateTime.Now.ToString("h:mm:ss.fff"),(++invokeCount).ToString()); if(invokeCount == 10)
{
invokeCount = 0;
autoEvent.Set();
}
}

System.Timers.Timer

System.Timers.Timer在内部使用System.Threading.Timer,并公开了更多的属性,如AutoReset, EnabledSynchronizingObject,这些属性允许配置回调的执行方式。此外,Tick事件允许注册多个处理程序。因此,一个定时器可以触发多个处理程序。还可以在计时器启动后更改处理程序。与System.Threading.Timer相似,其优点也是精度相对较高,与Windows操作系统时钟精度一致,大约15毫秒。因为默认(或者SynchronizingObject=null时)是基于线程池的,所以在任务执行时间较长或者线程池过载时,会出现延迟。但使用要更简便一些。

public partial class TimerFrom : Form
{
private System.Timers.Timer timer;
private void TimerFrom_Load(object sender, EventArgs e)
{
// 支持注册多个处理程序
timer.Elapsed += (sender, e) => { label1.Text = DateTime.Now.ToLongTimeString(); };
timer.Elapsed += (sender, e) => { Console.WriteLine(DateTime.Now.ToLongTimeString()); };
//自定义回调执行的方式(指定对象所在的线程),SynchronizingObject=null时在线程池上执行
timer.SynchronizingObject = this;
timer.AutoReset = true;
timer.Start();
}
}

本例中将SynchronizingObject属性设置为Form对象,因此Elapsed的处理程序在UI线程上执行,可以直接修改 label1.Text,如果SynchronizingObject属性为null,处理程序则是在线程池线程上执行,修改 label1.Text时需要通过Invoke或者BeginInvoke封送(marshal)到UI线程上运行。

System.Threading.PeriodicTimer

System.Threading.PeriodicTimer是 .NET 6中引入的定时器。它能方便地使用异步方式,它没有Tick事件,而是提供WaitForNextTickAsync方法处理定时任务。通常是使用While循环结合CancellationToken一起使用。和CancellationToken一起用的时候需要注意,如果CancellationToken被取消的时候会抛出一个OperationCanceledException需要考虑自己处理异常。相比之前的定时器来说,有下面几个特点:[1]

  1. 没有callback 来绑定事件;
  2. 不会发生重入,只允许有一个消费者,不允许同一个PeriodicTimer在不同的地方同时WaitForNextTickAsync,不需要自己做排他锁来实现不能重入;
  3. 异步化。之前的 timer 的 callback 都是同步的,使用新 timer 可以使用异步方法,避免了编写 Sync over Async 代码;
  4. Dispose 之后,实例就无法使用,并且 WaitForNextTickAsync 始终返回 false。
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
using (var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)))
{
try
{
while (await timer.WaitForNextTickAsync(cts.Token))
{
await Task.Delay(3000);
Console.WriteLine($"ThreadId is {Thread.CurrentThread.ManagedThreadId} --- Time is {DateTime.Now:HH:mm:ss}");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation cancelled");
}
}

小结

我们在开发过程中遇到的坑往往不是技术本身的坑,而是我们滥用没有掌握的技术导致的,在有多种技术方案可选的时候,通常只关注技术的优点,忽略了技术适用场景及其局限性。.NET中几种定时器各自都有其适用场景和不足,但都不支持高精度计时。了解这些有助于我们在开发过程中选择合适定时器,避免遇到问题后被动地替换。


  1. https://xie.infoq.cn/article/6aa23b6850abddf717a6c9fc9

.NET中有多少种定时器的更多相关文章

  1. wpf中应该使用c#四种定时器中的DispatcherTimer

    c#中有四种定时器 1:System.Threading.Timer 使用: private System.Threading.Timer timerClose; timerClose = new S ...

  2. Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用

    OC中的三种定时器:CADisplayLink.NSTimer.GCD 我们先来看看CADiskplayLink, 点进头文件里面看看, 用注释来说明下 @interface CADisplayLin ...

  3. 动手学习TCP:4种定时器

    上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...

  4. TCP四种定时器--学习笔记

    TCP使用四种定时器: 重传定时器(Retransmission Timer).坚持定时器(Persistent Timer).保活定时器(Keeplive Timer).时间等待定时器(Time_W ...

  5. javascript两种定时器的使用及其清除

    <!--示例代码如下:--><!DOCTYPE html> <html> <body> <p>A script on this page s ...

  6. Java语言中有4种访问修饰符

    转载:http://wuhaidong.iteye.com/blog/851754 Java语言中有4种访问修饰符 在Java语言中有4中访问修饰符:package(默认).private.publi ...

  7. 从51跳新唐cortex学习3——细说新唐两种定时器

    的定时器我们已经是相当熟悉啦.但是,对于第一次接触新唐,第一次接触cortex的定时器一样,都是加1计数,但是功能和容量变大了),包括4个32位定时器(里面放24位的数据),还有分频功能.TMER0到 ...

  8. Qt分析:Qt中的两种定时器(可是QObject为什么要提高定时器呢,没必要啊。。。)

    Qt有两种定时器,一种是QObject类的定时器,另一种是QTimer类的定时器.   (1)QObject类的定时器   QObject类提供了一个基本的定时器,通过函数startTimer()来启 ...

  9. cocos2dx三种定时器使用

         cocos2dx三种定时器的使用以及停止schedule.scheduleUpdate.scheduleOnce 今天白白跟大家分享一下cocos2dx中定时器的用法. 首先,什么是定时 ...

  10. java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

    java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类? Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种 ...

随机推荐

  1. gulp来处理html、css、js资源啦

    gulp 一般用于处理自动化任务,默认情况无法处理模块化,也不会用于大型项目,但它可以使用各种插件来编译 html.css.js 等资源. 不清楚如何使用 gulp 开启任务的朋友可以参考 gulp使 ...

  2. 论文解读(TAT)《 Transferable Adversarial Training: A General Approach to Adapting Deep Classifiers》

    Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:Transferable Adversarial Training: A General Approach ...

  3. Android 项目移植后运行黑屏

    在做Android项目的移植过程中,即把名为a的代码和样式全部复制到b项目后,只是对a项目内多余代码删除和修改包名,最后替换完后,点击=>运行,运行成功,但是演示机显示黑屏,看log也没有报错. ...

  4. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-14-playwright操作iframe-番外篇

    1.简介 通过前边三篇的学习,想必大家已经对iframe有了一定的认识和了解,今天这一篇主要是对iframe的一些特殊情况的介绍和讲解,主要从iframe的定位.监听事件和执行js脚本三个方面进行展开 ...

  5. MySQL允许远程登录的授权方法

    泛授权方式 数据库本地直接登录上数据库: mysql -h localhost -u root 然后执行以下命令,授权完后直接就可以远程连接上.mysql>GRANT ALL PRIVILEGE ...

  6. 特斯拉Dojo超算:AI训练平台的自动驾驶与通用人工智能之关键

    特斯拉公开Dojo超算架构细节,AI训练算力平台成为其自动驾驶与通用人工智能布局的关键一环 在近日举行的Hot Chips 34会议上,特斯拉披露了其自主研发的AI超算Dojo的详细信息.Dojo是一 ...

  7. flutter的json转dart model问题

    原文链接 原文链接 Preface 最近在做一个app,以后续用来找工作可以拿出来看看. 试试自己到产品设计能力,前后端能力等等. 中间遇到到一些有值得记录的点全部记录在此. Content json ...

  8. MySQL 日志管理、备份与恢复

    MySQL 日志管理.备份与恢复 ---MySQL 日志管理--- MySQL 的日志默认保存位置为 /usr/local/mysql/data vim /etc/my.cnf [mysqld] ## ...

  9. ElasticSearch系列——文档操作

    文章目录 Elasticsearch的增删查改(CURD) 一 CURD之Create 二 CURD之Update 三 CURD之Delete 四 CURD之Retrieve Elasticsearc ...

  10. [NSSCTF 2022 Spring Recruit]babyphp

    打开靶机,先看了一遍代码,发现要拿到flag,就必须先满足三个条件,即分别为a,b,c 第一层:需要a满足条件 isset($_POST['a'])&&!preg_match('/[0 ...