前言

定时器功能在日常开发中也是比较常用的,在 .Net 中实际上总共有五种定时器,分别是:System.Timers.TimerSystem.Threading.TimerSystem.Windows.Forms.TimerSystem.Web.UI.Timer (仅 .NET Framework)、System.Windows.Threading.DispatcherTimer

其中最常用的就是 System.Threading.Timer 基于线程池的定时器,相较于另外几种定时器,其安全性较高,适用性最强,因此本文将详细介绍此定时器的相关内容。

一、两类重载

参考:Timer 构造函数

1、 Timer(TimerCallback)

使用新创建的 Timer 对象作为状态对象,用一个无限周期和一个无限到期时间初始化 Timer 类的新实例。当循环任务达成时,可以在回调函数中将当前的 Timer 对象释放掉。

// 语法
public Timer (System.Threading.TimerCallback callback);

下面是一个简单示例:(在回调函数 TimerProc 中,我们可以通过将 Timer 对象释放掉,来结束循环过程)

using System;
using System.Threading; namespace Test.Test.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Program ex = new Program();
ex.StartTimer(4000); // 创建两个 Timer 对象
ex.StartTimer(1000);
Console.WriteLine("Press Enter to end the program.");
Console.ReadLine();
}
public void StartTimer(int dueTime)
{
Timer t = new Timer(new TimerCallback(TimerProc));
t.Change(dueTime, 5000); // 启动定时器
// dueTime:表示延迟调用的时间;
// 5000:表示回调的时间间隔,单位:毫秒
// 如果在回调中将 Timer 释放掉,则后续回调将无法发生
}
private void TimerProc(object state) // 入参对象为 Timer 对象
{
Timer t = (Timer)state;
t.Dispose(); // 调用一次就释放掉,或者添加条件释放
Console.WriteLine("The timer callback executes.");
}
}
}
// 输出结果:
// Press Enter to end the program.
// The timer callback executes.
// The timer callback executes.

2、Timer(TimerCallback, Object, Int32, Int32)

使用 32 位的有符号整数指定时间间隔,初始化 Timer 类的新实例。callback:回调函数名;state:包含回调方法要使用的信息的状态对象,可为空;dueTime:延迟调用的时间;period:重复回调的时间间隔。

// 语法
public Timer (System.Threading.TimerCallback callback, object? state, int dueTime, int period);

下面是一个示例:(关于线程自动重置类:AutoResetEvent 类

通过线程同步事件,演示不同时间间隔输出结果的区别
using System;
using System.Threading; class Program
{
static void Main()
{
// AutoResetEvent:表示一个线程同步事件,在等待线程释放后,收到信号时,自动重置
var autoEvent = new AutoResetEvent(false);
var statusChecker = new StatusChecker(10); // 实例化 StatusChecker 并设置最大循环次数
Console.WriteLine($"{DateTime.Now.ToString("HH:mm:dd.fff")} Creating timer.\n");
var stateTimer = new Timer(statusChecker.CheckStatus, autoEvent, 1000, 250);
// 1000 表示延迟 1s 开始执行;250 表示回调时间间隔为 0.25s
autoEvent.WaitOne(); // WaitOne:阻塞当前线程,直到 WaitHandle 接收到信号
stateTimer.Change(0, 500); // Change:0 不延迟立即启动执行;500 表示回调间隔为 0.5s
Console.WriteLine("\nChanging period to .5 seconds.\n");
autoEvent.WaitOne(); // WaitOne:阻塞当前线程,直到 WaitHandle 接收到信号
stateTimer.Dispose(); // 释放 Timer 对象
Console.WriteLine("\nDestroying timer.");
}
}
class StatusChecker
{
private int invokeCount; // 回调计数
private int maxCount; // 回调最大次数
public StatusChecker(int count)
{
invokeCount = 0;
maxCount = count;
}
public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine($"{DateTime.Now.ToString("HH: mm:ss.fff")} Checking status {(++invokeCount)}.");
if (invokeCount == maxCount)
{
invokeCount = 0;
// Set 将事件状态设置为有信号状态,允许一个或多个等待线程继续执行
autoEvent.Set();
}
}
}
// 输出结果:
// 11:26:22.571 Creating timer.
//
// 11:26:16.489 Checking status 1.
// 11:26:16.500 Checking status 2.
// 11:26:16.749 Checking status 3.
// 11:26:17.000 Checking status 4.
// 11:26:17.245 Checking status 5.
// 11:26:17.503 Checking status 6.
// 11:26:17.744 Checking status 7.
// 11:26:17.993 Checking status 8.
// 11:26:18.241 Checking status 9.
// 11:26:18.504 Checking status 10.
//
// Changing period to .5 seconds.
//
// 11:26:18.505 Checking status 1.
// 11:26:19.017 Checking status 2.
// 11:26:19.510 Checking status 3.
// 11:26:20.009 Checking status 4.
// 11:26:20.509 Checking status 5.
// 11:26:21.020 Checking status 6.
// 11:26:21.518 Checking status 7.
// 11:26:22.016 Checking status 8.
// 11:26:22.516 Checking status 9.
// 11:26:23.016 Checking status 10.
//
// Destroying timer.

与此重载类似用法的另外三个重载如下:

// 1、用 64 位整数表示时间间隔
Timer(TimerCallback, Object, Int64, Int64)
// 2、时间戳参数
// 时间戳语法:public TimeSpan (int hours, int minutes, int seconds);
// TimeSpan delayTime = new TimeSpan(0, 0, 1);
// 时间戳语法:public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds, int microseconds);
// TimeSpan intervalTime = new TimeSpan(0, 0, 0, 0, 250);
Timer(TimerCallback, Object, TimeSpan, TimeSpan)
// 3、用 32 位无符号整数来表示时间间隔
Timer(TimerCallback, Object, UInt32, UInt32)

二、属性 ActiveCount

获取当前活动的计时器数量。 活动计数器定义为,在未来某一时间点触发且尚未取消。

下面是一个简单的示例:

using System;
using System.Threading; class Program
{
static void Main()
{
var stateTimer = new Timer((para)=>{ }, null, 1000, 250);
var stateTimer2 = new Timer((para) => { }, null, 1000, 250);
Console.WriteLine($"1、Timer ActiveCount.{Timer.ActiveCount}");
stateTimer.Dispose(); // 释放 Timer 对象
Console.WriteLine($"\n2、Timer ActiveCount.{Timer.ActiveCount}");
stateTimer2.Dispose(); // 释放 Timer2 对象
Console.WriteLine($"\n3、Timer ActiveCount.{Timer.ActiveCount}");
Thread.Sleep(5000);
}
}
// 输出结果:
// 1、Timer ActiveCount.2
//
// 2、Timer ActiveCount.1
//
// 3、Timer ActiveCount.0

三、方法

1、Timer.Change 方法

更改计时器的延迟启动时间和方法循环调用之间的时间间隔。单位均为毫秒(ms)。

其和 Timer 的构造函数重载类似,都是有四个重载,之间只有参数不同,用法相同。

四种类型分别是:Int32(32 位正整数)、Int64(64 位正整数)、TimeSpan(时间戳)、UInt32(32 位无符号整数)。

下面例举一个时间为正整数的示例:

// 先创建一个 Timer 对象
var stateTimer = new Timer((para)=>{ }, null, 1000, 250);
// 调用变更对象的方法如下:1000 表示:延迟 1s 触发;500 表示间隔 0.5s 循环调用
stateTimer.Change(1000, 500);

2、Timer.Dispose 方法

此方法共有两个重载,分别是:Dispose()、Dispose(WaitHandle)。

Dispose()
// 释放由 Timer 实例使用的当前所有资源
Dispose(WaitHandle)
// 释放由 Timer 实例使用的当前所有资源,并在释放完成时发出信号

Dispose() 方法就是直接将 Timer 对象释放调,这里就不再赘述了,下面来看一个关于 Dispose(WaitHandle) 的示例:

using System;
using System.Threading; namespace TimerDispose
{
class Program
{
static Timer timer = null; //**声明一个全局变量,避免 Timer 对象后续没有调用时,被 GC回收
// ManualResetEvent 继承自 WaitHandle
// 是否手动重置事件(是 WaitHandle 的子类) false 初始状态为无信号 true 初始状态为有信号
static ManualResetEvent timerDisposed = null;
static void CreateAndStartTimer()
{
// 初始化 Timer,设置触发间隔为 2000 毫秒,设置 dueTime 参数为 Timeout.Infinite 表示不启动 Timer
timer = new Timer(TimerCallBack, null, Timeout.Infinite, 2000);
// 启动 Timer,设置 dueTime 参数为 0 表示立刻启动 Timer
//**先实例化再启动的目的是:避免在调用 Dispose 方法前,timer 对象还未完成赋值,所导致的空对象错误
timer.Change(0, 2000);
}
/// <summary>
/// TimerCallBack 方法是 Timer 每一次触发后的事件处理方法
/// </summary>
static void TimerCallBack(object state)
{
// Thread.Sleep(10000); // Change() 报错:System.ObjectDisposedException: 'Cannot access a disposed object.'
try
{
timer.Change(0, 1000);
}
catch (ObjectDisposedException) //**当 Timer 对象已经调用了 Dispose 方法后,再调用 Change 方法,会抛出 ObjectDisposedException 异常
{
Console.WriteLine("在 Timer.Dispose 方法执行后,再调用 Timer.Change 方法已经没有意义");
}
Thread.Sleep(10000); // 在 Change() 之后可正常运行
}
static void Main(string[] args)
{
CreateAndStartTimer();
Console.WriteLine("按任意键调用Timer.Dispose方法...");
Console.ReadKey();
timerDisposed = new ManualResetEvent(false);
// 调用 Timer 的 bool Dispose(WaitHandle notifyObject) 重载方法,来结束Timer的触发,
// 当线程池中的所有 TimerCallBack 方法都执行完毕后,Timer 会发一个信号给 timerDisposed
timer.Dispose(timerDisposed);
// WaitHandle.WaitOne() 方法会等待收到一个信号,否则一直被阻塞
timerDisposed.WaitOne();
timerDisposed.Dispose();
Console.WriteLine("Timer已经结束,按任意键结束整个程序...");
Console.ReadKey();
}
}
}

另外一个很不错的示例,是一个国外的高手所写,不仅考虑到了 Timer.Change 方法会抛出 ObjectDisposedExceptio n异常,他还给 WaitHandle.WaitOne 方法添加了超时限制(_disposalTimeout),并且还加入了逻辑来防止 Timer.Dispose 方法被多次重复调用,注意 Timer 的 bool Dispose(WaitHandle notifyObject) 重载方法是会返回一个 bool 值的,如果它返回了 false,那么表示 Timer.Dispose 方法已经被调用过了,代码如下所示:

一个更优秀的示例代码
using System;
using System.Threading; namespace TimerDispose
{
class SafeTimer
{
private readonly TimeSpan _disposalTimeout; private readonly System.Threading.Timer _timer; private bool _disposeEnded; public SafeTimer(TimeSpan disposalTimeout)
{
_disposalTimeout = disposalTimeout;
_timer = new System.Threading.Timer(HandleTimerElapsed);
} public void TriggerOnceIn(TimeSpan time)
{
try
{
_timer.Change(time, Timeout.InfiniteTimeSpan);
}
catch (ObjectDisposedException)
{
// race condition with Dispose can cause trigger to be called when underlying
// timer is being disposed - and a change will fail in this case.
// see
// https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
if (_disposeEnded)
{
// we still want to throw the exception in case someone really tries
// to change the timer after disposal has finished
// of course there's a slight race condition here where we might not
// throw even though disposal is already done.
// since the offending code would most likely already be "failing"
// unreliably i personally can live with increasing the
// "unreliable failure" time-window slightly
throw;
}
}
} //Timer每一次触发后的事件处理方法
private void HandleTimerElapsed(object state)
{
//Do something
} public void Dispose()
{
using (var waitHandle = new ManualResetEvent(false))
{
// returns false on second dispose
if (_timer.Dispose(waitHandle))
{
if (!waitHandle.WaitOne(_disposalTimeout))
{
throw new TimeoutException(
"Timeout waiting for timer to stop. (...)");
}
_disposeEnded = true;
}
}
}
}
}

参考:System.Threading.Timer如何正确地被Dispose

3、Timer.DisposeAsync 方法

其为上一部分的一种异步实现。

从 .NET Core 开始,就意味着 .NET 来到了一个全新的异步时代。无论是各种基础类库(比如 System.IO)、AspNet Core、还是 EFCore 等等,它们都逐渐支持异步操作。其不阻止线程的执行,带来高性能的同时还基本不需要更改原有的编码习惯,因此后续针对异步的编程肯定会越来越普遍。

当一个实体类同时实现了 Dispose 和 DisposeAsync,由于程序会先判断时候实现了 DisposeAsync 异步释放,所以一般优先调用异步释放。参考:熟悉而陌生的新朋友——IAsyncDisposable

终极参考:Timer 类

C# System.Threading.Timer 详解及示例的更多相关文章

  1. System.Threading.Timer的使用技巧

    转自:http://www.360doc.com/content/11/0812/11/1039473_139824496.shtml# System.Threading.Timer timer = ...

  2. System.Threading.Timer 定时器的用法

    System.Threading.Timer 是C# 中的一个定时器,可以定时(不断循环)执行一个任务.它是在线程上执行的,具有很好的安全性.为此  .Net Framework 提供了5个重载的构造 ...

  3. Oracle创建表语句(Create table)语法详解及示例、、 C# 调用Oracle 存储过程返回数据集 实例

    Oracle创建表语句(Create table)语法详解及示例 2010-06-28 13:59:13|  分类: Oracle PL/SQL|字号 订阅 创建表(Create table)语法详解 ...

  4. .NET中System.Diagnostics.Stopwatch、System.Timers.Timer、System.Threading.Timer 的区别

    1.System.Diagnostics.Stopwatch Stopwatch 实例可以测量一个时间间隔的运行时间,也可以测量多个时间间隔的总运行时间. 在典型的 Stopwatch 方案中,先调用 ...

  5. C# System.Threading.Timer 使用方法

    public class TimerHelper { System.Threading.Timer timer; public TaskSendMMS tasksendmms { get; set; ...

  6. python中threading模块详解(一)

    python中threading模块详解(一) 来源 http://blog.chinaunix.net/uid-27571599-id-3484048.html threading提供了一个比thr ...

  7. System.Threading.Timer使用心得

    System.Threading.Timer 是一个使用回调方法的计时器,而且由线程池线程服务,简单且对资源要求不高. "只要在使用 Timer,就必须保留对它的引用."对于任何托 ...

  8. System.Threading.Timer 使用

    //定义计时器执行完成后的回调函数 TimerCallback timecallback = new TimerCallback(WriteMsg); //定义计时器 System.Threading ...

  9. orakill和ALTER SYSTEM KILL SESSION详解

    --orakill和ALTER SYSTEM KILL SESSION详解[转]-----------------------------------------2013/11/05 一个用户进程偶尔 ...

  10. System.Threading.Timer如何正确地被Dispose

    System.Threading.Timer是.NET中一个定时触发事件处理方法的类(本文后面简称Timer),它背后依靠的是.NET的线程池(ThreadPool),所以当Timer在短时间内触发了 ...

随机推荐

  1. devexpress中dockManager保存布局后恢复不正常

    在使用dockManager保存布局后进行恢复发现不正常,与中间的gridcontorl接触的都不行.gridcontorl设置的填充是fill 所以在在界面上再添加一个PanelControl控件并 ...

  2. Java 中经常被提到的 SPI 到底是什么?

    layout: post categories: Java title: Java 中经常被提到的 SPI 到底是什么? tagline: by 子悠 tags: - 子悠 Java 程序员在日常工作 ...

  3. 推荐一款 .NET 编写的 嵌入式平台的开源仿真器--Renode

    Renode 是一个开发框架,通过让你模拟物理硬件系统来加速物联网和嵌入式系统开发. Renode 可以模拟 Cortex-M.RISC-V 等微控制器,不仅可以模拟 CPU指令,还可以模拟外设,甚至 ...

  4. os sys json模块

    Day19 os sys json 今日内容概要 os模块 sys模块 json模块 json模块实践 今日内容详细 一.os模块 os模块主要与代码运行所在的操作系统打交道 import os 1. ...

  5. 实践案例:平安健康的 Dubbo3 迁移历程总结

    本篇是 Apache Dubbo 的实践案例.感兴趣的朋友可以访问官网了解更多详情,或搜索关注官方微信公众号 Apache Dubbo 跟进最新动态. 1 背景 我们公司从15年开始就使⽤dubbo作 ...

  6. .net6+wpf制作指定局域网ip无法上网的arp欺诈工具

    摘一段来自网上的arp欺诈解释:ARP欺骗(ARP spoofing),又称ARP毒化(ARP poisoning,网络上多译为ARP病毒)或ARP攻击,是针对以太网地址解析协议(ARP)的一种攻击技 ...

  7. Hadoop如何保证自己的江湖地位?Yarn功不可没

    前言 任何计算任务的运行都离不开计算资源,比如CPU.内存等,那么如何对于计算资源的管理调度就成为了一个重点.大数据领域中的Hadoop之所以一家独大,深受市场的欢迎,和他们设计了一个通用的资源管理调 ...

  8. 推荐一款 在线+离线数据 同步框架 Dotmim.Sync

    移动智能应用可以分为在线模式.纯离线模式与"在线+离线"混合模式.在线模式下系统数据一般存储在服务器端的大中型数据库(如 SQL Server.Oracle.MySQL 等),移动 ...

  9. Day38:Lambda表达式

    Lambda表达式 1.1 概述 Lambda是JDK8开始后的一种新语法形式. 作用:简化函数式匿名内部类的代码写法. 简化格式: /*部类被重写方法的参数)->{ 被重写方法的方法体代码 } ...

  10. MySQL视图-触发器

    目录 一:视图 1.什么是视图? 2.为什么要用视图? 3.如何使用视图 4.反复拼接的繁琐(引入视图的作用) 5.解决方法 二:视图的应用 1.创建视图的格式: 2.查询视图层 3.查询Navica ...