写点什么

.NET 中有多少种定时器

  • 2023-12-08
    福建
  • 本文字数:3888 字

    阅读完需:约 13 分钟

.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,并公开了更多的属性,如AutoResetEnabledSynchronizingObject,这些属性允许配置回调的执行方式。此外,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 来绑定事件;


  1. 不会发生重入,只允许有一个消费者,不允许同一个PeriodicTimer在不同的地方同时WaitForNextTickAsync,不需要自己做排他锁来实现不能重入;


  1. 异步化。之前的 timer 的 callback 都是同步的,使用新 timer 可以使用异步方法,避免了编写 Sync over Async 代码;


  1. 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 中几种定时器各自都有其适用场景和不足,但都不支持高精度计时。了解这些有助于我们在开发过程中选择合适定时器,避免遇到问题后被动地替换解决方案。


文章转载自:czwy

原文链接:https://www.cnblogs.com/czwy/p/17862702.html


用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
.NET中有多少种定时器_.net_快乐非自愿限量之名_InfoQ写作社区