.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. 基于Linux的三种防火墙(IPtables、Firewall、UFW)

    学而不思则罔,思而不学则殆. 导航 IPtables Firewall UFW 对比总结 IPtables部分 1.IPtables 四表五链. 四表:filter.nat.raw.mangle. 五 ...

  2. Nep2023的wp

    0x00 闲言碎语 2023.8.14 记录11-13的紧张刺激.46名结赛. 非常高兴能够参加NepCTF2023,以一个初出茅庐的新人的身份参加.ctf的乐趣在于学习和探索,同时我也有想证明自己的 ...

  3. c++算法之动态规划:01背包

    什么是动态规划? 动态规划算法(dynamic programing),是一种由递推为基础的比贪心更稳定的一种优化策略,为运筹学的一部分.就是通过以递推为基础的手段非暴力求出最值. 它的总体思想其实就 ...

  4. 【日常踩坑】从 SSLEOFError 到正确配置 Proxy

    目录 踩坑 代理服务器 普通的代理服务器 因国家法律规定,部分内容已删除,完整内容请查看文章末尾链接 代理配置 追根溯源 urllib3 pip 万恶之源 urllib 参考资料 本文主要参考 Pyt ...

  5. Pandas 使用教程 Series、DataFrame

    目录 Series (一维数据) 指定索引值 使用 key/value 对象,创建对象 设置 Series 名称参数 DataFrame(二维数据) 使用字典(key/value)创建 loc 属性返 ...

  6. 使用 Laf 一周内上线美术狮 AI 绘画小程序

    "美术狮 AI 绘画"(以下简称"美术狮"),是我们小团队的一次尝试,定位是人人都可以上手的,充满创意的,理解中文和中国文化的图片生成工具. 在完善图像模型和论 ...

  7. 鞭尸没 jj

      提前退役了.现在我想说一点无关紧要的闲话.   与其说是 OI 回忆录,不如说是对这主线明确的六年做的一个梳理,倒不一定 OI 强相关. 壹.零度下的相遇 视线就这样交叠 与你   最初接触到 O ...

  8. containerd镜像拉取配置

    背景: 公司要求部署最一套新版的k8s系统来部署生产应用,说实话很头疼.因为k8s自1.23版本之后就用不docker作为容器的默认运行时了,而是采用的containerd,这就带来了一系列的问题.没 ...

  9. 聊聊基于Alink库的推荐系统

    概述 Alink提供了一系列与推荐相关的组件,从组件使用得角度来看,需要重点关注如下三个方面: 算法选择 推荐领域有很多算法,常用的有基于物品/用户的协同过滤.ALS.FM算法等.对于不同的数据场景, ...

  10. c++枚举详细介绍以及具体用法

    C++ 中的枚举(Enumeration)是一种用于定义命名常量集合的数据类型.枚举可以提高代码的可读性和可维护性,让您可以使用有意义的名称来表示特定的取值,而不必使用原始的数字常量. 枚举的基本语法 ...