0


C#初级_定时器

文章目录

一、引言

在开发中,会遇到并行处理的需求。
有时只需要使用task(底层是创建个线程)来处理一下就好了。而有时则在并行处理的基础上还有时间的要求,较常见的就是每隔一定时间处理一次。当然,这用task肯定可以实现,但是时间这块得自己控制,无疑增加了工作量和不确定性。
.NET提供了叫做定时器(timer,也叫计时器)的类,它在并行处理的基础上,带了时间参数的设置,可以满足这一需求。

其实本文标题与其叫C#定时器,不如叫.NET定时器好些。因为这边介绍的定时器是.NET中的东西,不只针对C#(不过调用形式是C#的),.NET平台下的其他语言也可以用。不过.NET下的语言,用C#的可能相对多一些,所以很多时候.NET的东西也被叫做C#的东西,当然这种叫法不是很规范。


二、Timers

.NET提供了两种定时器用于多线程环境:

  • System.Threading.Timer,它会以固定间隔在ThreadPool线程上执行回调函数。
  • System.Timers.Timer,默认情况下,它会以固定间隔在ThreadPool线程上触发一个事件。

⚠注意:
一些.NET实现下还有其它类型的定时器:

  • System.Windows.Forms.Timer,从名字中就能看出来,它是WinForms的定期触发事件的组件,是为单线程环境设计的。
  • System.Web.UI.Timer,这是一个ASP.NET组件,以固定间隔执行异步或同步的网页回发。
  • System.Windows.Threading.DispatcherTimer,集成到Dispatcher队列中的定时器,它会按照指定的时间间隔和指定的优先级进行处理。

1. System.Threading.Timer

1.1. 简单使用

System.Threading.Timer类能以指定间隔调用委托(连续或单次)。该委托在ThreadPool线程中执行。

创建System.Threading.Timer对象时,你需要指定一个TimerCallback委托来定义回调方法、一个传递给回调函数的可选state对象、以及首次调用回调函数之前的延迟时间和连续回调调用的时间间隔。要取消一个挂起(pending)的定时器,可以调用Timer.Dispose方法。

下面代码示例创建了一个定时器,在创建一秒后首次调用委托,之后每两秒调用一次。示例中的state对象用于计算调用委托的次数。当委托被调用10次后,计时器停止。

usingSystem;usingSystem.Threading;usingSystem.Threading.Tasks;classProgram{privatestaticTimer timer;staticvoidMain(string[] args){var timerState =newTimerState{ Counter =0};

        timer =newTimer(callback:newTimerCallback(TimerTask),state: timerState,dueTime:1000,period:2000);while(timerState.Counter <=10){
            Task.Delay(1000).Wait();}

        timer.Dispose();
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: done.");}privatestaticvoidTimerTask(object timerState){
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: starting a new callback.");var state = timerState asTimerState;
        Interlocked.Increment(ref state.Counter);}classTimerState{publicint Counter;}}

1.2. 注意点

使用TimerCallback委托来指定希望Timer执行的方法。TimerCallback委托的签名如下:

voidTimerCallback(Object state)

定时器委托是在定时器被构造之后(即new了之后)指定的,且无法更改。该方法不会在创建定时器的线程上执行;它是在系统提供的ThreadPool线程上执行。

Timer类有着与系统时钟相同的分辨率。这意味着,如果周期小于系统时钟的分辨率,TimerCallback委托将按照系统时钟的分辨率定义的时间间隔执行,在Win7和Win8系统上大约是15ms。可以使用Change方法更改到期时间和周期,或禁用定时器。

⚠注意:
只要使用Timer,就必须保持对它的引用。
与其它托管对象一样,当没有对Timer的引用时,Timer也会受到GC(垃圾回收器)的影响。即使Timer仍然处于活动状态,也不会阻止它被回收。
使用的系统时钟与GetTickCount使用的时钟相同,不受timeBeginPeriod和timeEndPeriod改变的影响。

当不再需要定时器时,使用Dispose方法释放定时器所持有的资源。注意,回调函数可能在Dispose()方法重载被调用之后才发生,因为定时器队列回调函数由线程池线程执行。可以使用Dispose(waitHandle)方法重载来等待,直到所有回调完成。

该定时器执行的回调方法是可重入的(多个线程同时执行,第一个线程还没执行完,第二个线程又进去执行了),因为它是在ThreadPool线程上调用的。如果定时器间隔小于执行回调所需的时间,或者如果所有线程池线程都在使用并且多次排队,则可以在两个线程池线程上同时执行回调。

⚠注意:
System.Threading.Timer是一个简单的轻量级定时器,它使用回调方法,由线程池线程提供服务。不建议在WinForms中使用,因为它的回调函数不会在UI线程上发生。System.Windows.Forms.Timer更适用于WinForms。对于基于服务器的定时器功能,可以考虑使用System.Timers.Timer,它会引发事件并具有额外功能。

2. System.Timers.Timer

2.1. 概述

另一个用于多线程环境的定时器是System.Timers.Timer,默认情况下,它会在ThreadPool线程中引发一个事件。

当创建System.Timers.Timer对象时,可以指定引发事件的时间间隔。使用Enabled属性来指定定时器是否引发事件。如果要指定只引发一次Elapsed事件,将AutoReset设置为false。AutoReset属性的默认值为true,意味着在interval属性定义的时间间隔内会定时引发Elapsed事件。

🔺2.2. 注意点

Timer组件是一个基于服务器的定时器,它会在经过Interval属性设置的毫秒数之后引发一个Elapsed事件。使用AutoReset属性配置Timer对象,使其只引发一次或重复引发事件。通常,Timer对象声明在类层级,以便你需要它时,它就在作用域中。

// 声明在类层级大概是这个意思?// 不是定义在局部的,而是整个类的成员变量(字段)// 这样你才类中任意地方都可以去操作它classA{Timer _timer;}

然后可以处理它的Elapsed事件来进行常规处理。例如,假设你有一个服务器,它必须每周7天、每天24小时运行。你可以创建一个使用Timer对象的服务来定期检查服务器,并确保系统已启动并运行。如果系统没有响应,服务可以尝试重新启动服务器并通知管理员。

⚠注意:
该Timer类并不适用所有的.NET实现和版本,例如,.NET Standard 1.6以及更低版本。在这些情况下,你可以使用System.Threading.Timer类。
从这句话中有种System.Timers.Timer的使用优先级比System.Threading.Timer高的感觉。

该Timer类实现了IDisposable接口。当你使用完该类后,应该销毁它。要直接销毁该类,在try/catch块中调用它的dispose方法。间接销毁,可以使用using。

基于服务器的System.Timers.Timer类是为多线程环境中的工作线程而设计的。服务器定时器可以在线程之间移动来处理引发的Elapsed事件,在引发事件及时性上比Windows定时器更精确。

System.Timers.Timer组件根据Interval属性的值(以毫秒为单位)引发Elapsed事件。通过处理此事件来执行所需的处理过程。例如,假设你有一个在线销售应用程序,它不断向数据库发布销售订单。编译运输指令的服务对一批订单进行操作而不是单独处理每个订单。你可以使用Timer来每30分钟启动批处理。

⚠注意:
System.Timers.Timer类具有与系统时钟相同的分辨率。
这意味着如果Interval属性小于系统时钟分辨率,则Elapsed事件将按照系统时钟分辨率定义的时间间隔触发。

Timer组件捕获并抑制事件处理程序为Elapsed事件抛出的所有异常(也就是说,在Elapsed事件处理器中抛出的异常,你在其它线程中无法直接感受到。这就可能导致,如果你在Elapsed事件处理器中没有添加异常处理机制,并且里面抛出异常了,从线程外看好像啥也没发生)。但是要注意,对于异步执行并包含await操作符(在C#中)或await操作符(在VB中)的事件处理程序则不然。这些事件处理程序中抛出的异常会传回调用线程,如下所示:

usingSystem;usingSystem.Threading.Tasks;usingSystem.Timers;classExample{staticvoidMain(){Timer timer =newTimer(1000);
      timer.Elapsed +=async( sender, e )=>awaitHandleTimer();
      timer.Start();
      Console.Write("Press any key to exit... ");
      Console.ReadKey();}privatestaticTaskHandleTimer(){
     Console.WriteLine("\nHandler not implemented...");thrownewNotImplementedException();}}// The example displays output like the following://   Press any key to exit...//   Handler not implemented...//   //   Unhandled Exception: System.NotImplementedException: The method or operation is not implemented.//      at Example.HandleTimer()//      at Example.<<Main>b__0>d__2.MoveNext()//   --- End of stack trace from previous location where exception was thrown ---//      at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c__DisplayClass2.<ThrowAsync>b__5(Object state)//      at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)//      at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)//      at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()//      at System.Threading.ThreadPoolWorkQueue.Dispatch()

若SynchronizingObject属性为null,则Elapsed事件在ThreadPool线程中引发。若Elapsed事件的处理耗时长于Interval,则该事件可能会在另一个ThreadPool线程上再次引发。该情况下,事件处理程序应该是可重入的(reentrant)。

⚠注意:
事件处理方法可能运行在一个线程,同时另一个线程调用Stop方法或将Enabled属性设置为false。这可能导致在定时器停止后引发Elapsed事件。Stop方法的示例代码展示了一种避免这种竞争条件的方法。

后面这部分等学了await再看。

三、总结

总的来说,System.Threading.Timer和System.Timers.Timer表面上主要异同是,
1️⃣前者是直接调用委托,而后者是引发事件。
2️⃣两者都是运行在系统线程池线程上的
3️⃣两者时钟分辨率都等于系统时钟分辨率
4️⃣前者可能较轻量级,后者是服务器级别的(但这点很模糊,我的理解是一般的桌面程序用前者即可,如果程序相对较大,可能用后者好)
5️⃣System.Timers.Timer会捕获并抑制Elapsed事件处理器抛出的所有异常。

如果你想将这两者用于WPF应用,尤其是MVVM的VM中来实现多线程改变绑定的数据,那可能会达不到预期效果,因为在WPF框架的设定下,非UI线程直接或间接访问UI线程是不合法的。如ObservableCollection之类的集合跨线程访问时,大多会报错System.NotSupportedException。

最后根据官方文档描述,一般都是推荐用后者的。

标签: c#

本文转载自: https://blog.csdn.net/BadAyase/article/details/126384462
版权归原作者 MelonSuika 所有, 如有侵权,请联系我们删除。

“C#初级_定时器”的评论:

还没有评论