.NET中有多少种定时器

.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/229000.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网络编程】-- 02 端口、通信协议

网络编程 3 端口 端口表示计算机上的一个程序的进程 不同的进程有不同的端口号!用来区分不同的软件进程 被规定总共0~65535 TCP,UDP:65535 * 2 在同一协议下,端口号不可以冲突占用 端口分类: 公有端口:0~1023 HT…

Linux环境下用yum安装postgres15

1. 下载PostgreSQL 15 安装包 在官网选择对应版本的安装包 https://www.postgresql.org/download/ Linux | CentOS 7 | PostgreSQL 15 2. 安装PostgreSQL 15 sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-la…

chrome安装jsonview

写在前面 通过jsonview可以实现,当http响应时application/json时直接在浏览器格式化显示,增加可读性。本文看下如何安装该插件到chrome中。 1:安装 首先在这里 下载插件包,然后解压备用。接着在chrome按照如下步骤操作&#xf…

小程序一键生成工具哪个好?

在这个数字化时代,小程序已经成为商家吸引客户、提升业务的重要工具。但是,传统的小程序开发方式既费时又费力,让许多商家望而却步。 现在,有了乔拓云小程序模板开发平台,一切都变了。 乔拓云提供了大量精心设计的模板…

销售技巧培训之如何提高手机销售技巧

销售技巧培训之如何提高手机销售技巧 随着科技的迅速发展,手机已成为我们日常生活中不可或缺的一部分。作为一名手机销售员,了解手机销售技巧是必不可少的。本文将通过案例分析与实践,为你揭示手机销售的奥秘。 一、了解客户需求 在销售过程…

自动化运维工具-ansible部署

首先我们来谈一下,为什么要引入自动化运维呢? 引入自动化运维的目的是为了提高运维效率、降低人工操作的错误率、减少重复性的工作、提高系统的可靠性和稳定性。传统的手动运维方式存在以下问题: 出现了大量的人工干预,运维人员需…

Web端在线云剪辑方案

视频内容已经成为企业传播信息、展示品牌形象的重要手段。然而,视频制作并非易事,需要专业的技术和设备支持。为了帮助企业解决这个问题,美摄科技推出了Web端在线云剪辑方案,提供广播级专业技术赋能,帮助企业快速搭建视…

最新V2board面板支付设置(四)

顺哥博客 支付方式一(推荐): USDT收款: 特点:自己的USDT钱包收款,没有中间商,无手续费,实时到账项目开源地址:【点击进入】把文件usdtwebhook.php放到网站此目录下&…

使用命令行移除VSAN中故障磁盘

原创作者:运维工程师 谢晋 使用命令行移除VSAN中故障磁盘 前提故障盘移除 前提 客户有套VSAN环境内有一台服务器的磁盘组出现了一块故障的数据盘,但该盘已经处于完全掉线状态,无法进行正常移除。如下图: 如果遇到这种情况&am…

QT作业2

使用手动连接,将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中,在槽函数中判断ui界面上输入的账号是否为"admin",密码是否为…

为什么Java程序员需要掌握多线程?揭秘并发编程的奥秘

为什么Java程序员需要掌握多线程?揭秘并发编程的奥秘 个人简介前言多线程对于Java的意义📌1.提高程序性能:📌2 提高用户体验:📌3支持并发处理:📌4 资源共享和同步:&#…

el-tree数据量过大,造成浏览器卡死、崩溃

el-tree数据量过大,造成浏览器卡死、崩溃 场景:树形结构展示,数据超级多,超过万条,每次打开都会崩溃 我这里采用的是引入新的插件虚拟树,它是参照element-plus 中TreeV2改造vue2.x版本虚拟化树形控件&…

Navicat 技术指引 | 适用于 GaussDB 分布式的数据迁移工具

Navicat Premium(16.3.3 Windows 版或以上)正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能,还提供强大的高阶功能(如模型、结…

选自《洛谷深入浅出进阶篇》——欧拉函数+欧拉定理+扩展欧拉定理

欧拉函数: 欧拉函数定义: 1~n中与n互质的数的个数。 比如 欧拉函数是积性函数:(也就是)当 n与m互质的时候: 由算术基本定理,我们可以设n,那么我们只要计算出的取值就能求出的取…

snakeyaml编辑yaml文件并覆盖注释

文章目录 前言技术积累实战演示1、引入maven依赖2、覆盖注释工具类3、snakeyaml工具类4、测试用例5、测试效果展示 写在最后 前言 最近在做一个动态整合框架的项目,需要根据需求动态组装各个功能模块。其中就涉及到了在application.yaml中加入其他模块的配置&#…

Windows版Minio使用教程(启动,登录,修改密码)

1 、下载安装包 进入官网下载安装包: MinIO | 高性能、支持原生 Kubernetes的对象存储 信任程序安装,就可以啦 2、启动MinIO 第一步,找到minio.exe所在的目录,在地址栏输入cmd进入cmd窗口。 第二步,输入.\minio.exe…

项目状态报告

《项目状态报告》 第1章 当前阶段的工作完成情况 1.1 概述 1.2 各子系统详细进度 第2章 偏差及偏差原因 第3章 偏差纠正措施 第4章 拟进行的变更 第5章 存在的风险及应对计划 第6章 下一阶段主要工作

delphi android打开外部文件,报错android.os.FileUriExposedException解决方法

Android 7.0强制启用了被称作 StrictMode的策略,带来的影响就是你的App对外无法暴露file://类型的URI了。 如果你使用Intent携带这样的URI去打开外部App(比如:打开系统相机拍照),那么会抛出FileUriExposedException异常。 Delphi 为Android…

Python源码30:海龟画图turtle画紫色的小熊

turtle模块是一个Python的标准库之一,它提供了一个基于Turtle graphics的绘图库。Turtle graphics是一种流行的绘图方式,它通过控制一个小海龟在屏幕上移动来绘制图形。 turtle模块可以让您轻松地创建和控制海龟图形,从而帮助您学习Python编…

yolo目标检测+目标跟踪+车辆计数+车辆分割+车道线变更检测+速度估计

这个项目使用YOLO进行车辆检测,使用SORT(简单在线实时跟踪器)进行车辆跟踪。该项目实现了以下任务: 车辆计数车道分割车道变更检测速度估计将所有这些详细信息转储到CSV文件中 车辆计数是指在道路上安装相应设备,通过…