原标题:.NET中Timer 如何正确地被Dispose

转自:PowerCoder

cnblogs.com/OpenCoder/p/10728839.html

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

我们启动Timer后,如果我们想停止它,必须要用到Timer.Dispose方法,该方法会让Timer停止启动新的线程去执行事件处理方法

但是已经在线程池(ThreadPool)中处理和排队的事件处理方法还是会被继续执行,而Timer.Dispose方法会立即返回,它并不会被阻塞来等待剩下在线程池(ThreadPool)中处理和排队的事件处理方法都执行完毕。

所以这个时候我们需要一个机制来知道当Timer.Dispose方法被调用后,剩下在线程池(ThreadPool)中处理和排队的事件处理方法,是否都已经被执行完毕了。

这个时候我们需要用到Timer的bool Dispose(WaitHandle notifyObject)重载方法,这个Dispose方法会传入一个WaitHandle notifyObject参数,当Timer剩下在线程池(ThreadPool)中处理和排队的事件处理方法都执行完毕后,Timer会给Dispose方法传入的WaitHandle notifyObject参数发出一个信号,而我们可以通过WaitHandle.WaitOne方法来等待该信号,在收到信号前WaitHandle.WaitOne方法会被一直阻塞,代码如下所示(基于.NET Core控制台项目):

usingSystem;

usingSystem.Threading;

namespaceTimerDispose

{

classProgram

{

staticTimer timer = null;

staticManualResetEvent timerDisposed = null; //ManualResetEvent继承WaitHandle

staticinttimeCount = 0;

staticvoidCreateAndStartTimer()

{

//初始化Timer,设置触发间隔为2000毫秒

timer = newTimer(TimerCallBack, null, 0, 2000);

}

///

///TimerCallBack方法是Timer每一次触发后的事件处理方法

///

staticvoidTimerCallBack(objectstate)

{

//模拟做一些处理逻辑的事情

timeCount++; //每一次Timer触发调用TimerCallBack方法后,timeCount会加1

//当timeCount为100的时候,调用Timer.Change方法来改变Timer的触发间隔为1000毫秒

if(timeCount == 100)

{

timer.Change( 0, 1000);

}

}

staticvoidMain(string[] args)

{

CreateAndStartTimer();

Console.WriteLine( "按任意键调用Timer.Dispose方法...");

Console.ReadKey();

timerDisposed = newManualResetEvent( false);

timer.Dispose(timerDisposed); //调用Timer的bool Dispose(WaitHandle notifyObject)重载方法,来结束Timer的触发,当线程池中的所有TimerCallBack方法都执行完毕后,Timer会发一个信号给timerDisposed

timerDisposed.WaitOne(); //WaitHandle.WaitOne()方法会等待收到一个信号,否则一直被阻塞

timerDisposed.Dispose();

Console.WriteLine( "Timer已经结束,按任意键结束整个程序...");

Console.ReadKey();

}

}

}

但是我们上面的代码中的TimerCallBack事件处理方法有一个逻辑,也就是当timeCount变量增加到100的时候,我们会调用Timer.Change方法,更改Timer的触发间隔为1000毫秒。

而Timer.Change方法是不能够在Timer.Dispose方法后调用的,也就是说当一个Timer调用了Dispose方法后,就不能再调用Timer.Change方法了,否则Timer.Change方法会抛出ObjectDisposedException异常,对此MSDN上的解释如下:

If the callback uses the Change method to set the dueTime parameter to zero, a race condition can occur when the Dispose(WaitHandle) method overload is called: If the timer queues a new callback before the Dispose(WaitHandle) method overload detects that there are no callbacks queued, Dispose(WaitHandle) continues to block; otherwise, the timer is disposed while the new callback is being queued, and an ObjectDisposedException is thrown when the new callback calls the Change method.

然而在我们的代码中调用Timer.Dispose方法和TimerCallBack事件处理方法是并行的,因为Timer.Dispose方法是在程序主线程上执行的,而TimerCallBack事件处理方法是在线程池(ThreadPool)中的线程上执行的,所以Timer.Dispose方法执行后,很有可能会再执行TimerCallBack事件处理方法,这时候如果恰好timeCount变量也增加到100了,会导致Timer.Change方法在Timer.Dispose方法后执行,抛出ObjectDisposedException异常。

对此我们要对我们的代码稍作更改,在TimerCallBack事件处理方法中来捕捉ObjectDisposedException异常:

usingSystem;

usingSystem.Threading;

namespaceTimerDispose

{

classProgram

{

staticTimer timer = null;

staticManualResetEvent timerDisposed = null; //ManualResetEvent继承WaitHandle

staticinttimeCount = 0;

staticvoidCreateAndStartTimer()

{

//初始化Timer,设置触发间隔为2000毫秒

timer = newTimer(TimerCallBack, null, 0, 2000);

}

///

///TimerCallBack方法是Timer每一次触发后的事件处理方法

///

staticvoidTimerCallBack(objectstate)

{

//模拟做一些处理逻辑的事情

timeCount++; //每一次Timer触发调用TimerCallBack方法后,timeCount会加1

//当timeCount为100的时候,调用Timer.Change方法来改变Timer的触发间隔为1000毫秒

if(timeCount == 100)

{

//添加try catch代码块,来捕捉Timer.Change方法抛出的ObjectDisposedException异常

try

{

timer.Change( 0, 1000);

}

catch(ObjectDisposedException)

{

//当Timer.Change方法抛出ObjectDisposedException异常后的处理逻辑

Console.WriteLine( "在Timer.Dispose方法执行后,再调用Timer.Change方法已经没有意义");

}

}

}

staticvoidMain(string[] args)

{

CreateAndStartTimer();

Console.WriteLine( "按任意键调用Timer.Dispose方法...");

Console.ReadKey();

timerDisposed = newManualResetEvent( false);

timer.Dispose(timerDisposed); //调用Timer的bool Dispose(WaitHandle notifyObject)重载方法,来结束Timer的触发,当线程池中的所有TimerCallBack方法都执行完毕后,Timer会发一个信号给timerDisposed

timerDisposed.WaitOne(); //WaitHandle.WaitOne()方法会等待收到一个信号,否则一直被阻塞

timerDisposed.Dispose();

Console.WriteLine( "Timer已经结束,按任意键结束整个程序...");

Console.ReadKey();

}

}

}

所以这样我们可以防止Timer.Change方法在Timer.Dispose方法后意外抛出ObjectDisposedException异常,至少异常抛出时我们是有代码去处理的。

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

usingSystem;

usingSystem.Threading;

namespaceTimerDispose

{

classSafeTimer

{

privatereadonlyTimeSpan _disposalTimeout;

privatereadonlySystem.Threading.Timer _timer;

privatebool_disposeEnded;

publicSafeTimer(TimeSpan disposalTimeout)

{

_disposalTimeout = disposalTimeout;

_timer = newSystem.Threading.Timer(HandleTimerElapsed);

}

publicvoidTriggerOnceIn(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每一次触发后的事件处理方法

privatevoidHandleTimerElapsed(objectstate)

{

//Do something

}

publicvoidDispose()

{

using( varwaitHandle = newManualResetEvent( false))

{

// returns false on second dispose

if(_timer.Dispose(waitHandle))

{

if(!waitHandle.WaitOne(_disposalTimeout))

{

thrownewTimeoutException(

"Timeout waiting for timer to stop. (...)");

}

_disposeEnded = true;

}

}

}

}

}

可以参考这个链接(https://stackoverflow.com/questions/13396440/c-sharp-system-threading-timer-wait-for-dispose)查看详情,需要注意的是里面有说到几点:

第1点:

Timer.Dispose(WaitHandle) can returnfalse.

It does so incaseit 's already been disposed (i had to look at the source code).

In that case it won't setthe WaitHandle - so don 't wait on it!

(Note: multiple disposal should be supported)

也就是说如果Timer的bool Dispose(WaitHandle notifyObject)重载方法返回了false,Timer是不会给WaitHandle notifyObject参数发出信号的,所以当Dispose方法返回false时,不要去调用WaitHandle.WaitOne方法。

第2点:

Timer.Dispose(WaitHandle) does notwork properly with-Slim waithandles, ornotasone would expect. For example, the following does notwork (it blocks forever): using( varmanualResetEventSlim = newManualResetEventSlim())

{

timer.Dispose(manualResetEventSlim.WaitHandle);

manualResetEventSlim.Wait();

}

也就是说不要用ManualResetEventSlim,否则WaitHandle.WaitOne方法会一直阻塞下去。

.NET的垃圾回收机制GC会回收销毁System.Threading.Timer

有一点需要注意,一旦我们创建一个Timer对象后,它就自己在那里运行了,如果我们没有变量引用创建的Timer对象,那么.NET的垃圾回收机制GC会随时销毁我们创建的Timer对象,例如下面代码:

usingSystem;

usingSystem.Threading;

namespaceTimerDispose

{

classProgram

{

staticvoidCreateAndStartTimer()

{

//初始化Timer,设置触发间隔为2000毫秒

//由于我们这里创建的Timer对象没有被任何变量引用,只存在于方法CreateAndStartTimer中,所以.NET的垃圾回收机制GC会随时销毁该Timer对象

newTimer(TimerCallBack, null, 0, 2000);

}

///

///TimerCallBack方法是Timer每一次触发后的事件处理方法

///

staticvoidTimerCallBack(objectstate)

{

//模拟做一些处理逻辑的事情

}

staticvoidMain(string[] args)

{

CreateAndStartTimer();

Console.WriteLine( "按任意键结束整个程序...");

Console.ReadKey();

}

}

}

上面代码的问题在于我们在CreateAndStartTimer方法中创建的Timer对象,没有被任何外部变量引用,只存在于CreateAndStartTimer方法中,所以一旦CreateAndStartTimer方法执行完毕后,Timer对象随时可能会被.NET的垃圾回收机制GC销毁,而这可能并不是我们期望的行为。

对此有如下解决方案:

在CreateAndStartTimer方法中创建Timer对象后,将其指定给一个程序全局都可以访问到的变量:

usingSystem;

usingSystem.Threading;

namespaceTimerDispose

{

classProgram

{

//变量timer,用于引用CreateAndStartTimer方法内部创建的Timer对象

staticTimer timer = null;

staticvoidCreateAndStartTimer()

{

//初始化Timer,设置触发间隔为2000毫秒

//将创建的Timer对象,指定给一个程序全局都可以访问到的变量timer,防止Timer对象被.NET的垃圾回收机制GC销毁

timer = newTimer(TimerCallBack, null, 0, 2000);

}

///

///TimerCallBack方法是Timer每一次触发后的事件处理方法

///

staticvoidTimerCallBack(objectstate)

{

//模拟做一些处理逻辑的事情

}

staticvoidMain(string[] args)

{

CreateAndStartTimer();

Console.WriteLine( "按任意键结束整个程序...");

Console.ReadKey();

}

}

}

由于现在CreateAndStartTimer方法内部创建的Timer对象,现在可以通过变量timer被整个程序访问到,所以就不会被.NET的垃圾回收机制GC销毁掉了。返回搜狐,查看更多

责任编辑:

c# timer 销毁_.NET中Timer 如何正确地被Dispose相关推荐

  1. c# timer 销毁_如果表单应用程序关闭C#,如何防止发生Timer Elapsed事件

    我在 Windows窗体应用程序(Visual C#)中有一个计时器,当我想退出应用程序时,它会导致问题. 计时器被定义为表单类的成员: partial class Form1 { //These a ...

  2. matlab中的timer模块,[转载]Matlab中Timer的使用

    Matlab中Timer的使用 鉴于Matlab中缺乏多线程机制,使用Timer无疑是一个很重要的工具,Matlab中Timer是一个Java对象. (1)Timer 的定义 t=timer(); 设 ...

  3. 中运用_舞蹈中,如何正确运用呼吸?

    1.正确的呼吸赋予舞蹈生命力.正确的呼吸方式,能有效地控制身体,更好地完成舞蹈动作,进而突出舞蹈节奏.舞蹈风格,将张力与爆发力做到最大.在跳舞过程中,正确运用呼吸是一个慢慢养成的习惯: 2.做到气顺力 ...

  4. Java 中Timer和TimerTask 定时器和定时任务使用的例子

    转载自  Java 中Timer和TimerTask 定时器和定时任务使用的例子 这两个类使用起来非常方便,可以完成我们对定时器的绝大多数需求 Timer类是用来执行任务的类,它接受一个TimerTa ...

  5. java中timer类包_Java~util包中Timer的使用, 演示cancel方法 和 对比schedule和scheduleAtFixedRate方法...

    Timer Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类 执行计划任务的代码要放人TimerTask的子类中,因为TimerTask是一个抽象类.而且要重写其run方 ...

  6. C#中Timer组件用法

    Timer组件是也是一个WinForm组件了,和其他的WinForm组件的最大区别是:Timer组件是不可见的,而其他大部分的组件都是都是可见的,可以设计的.Timer组件也被封装在名称空间Syste ...

  7. Java程序中Timer的用法

    Java程序中Timer的用法 import java.io.IOException; import java.util.Timer; public class CheckTimer {/*** @p ...

  8. java step1:基础知识5(java中Timer和TimerTask的使用)

    1.定时任务:java中Timer和TimerTask的使用 转载自http://batitan.iteye.com/blog/253483 转载于:https://www.cnblogs.com/s ...

  9. python timer使用-python中timer定时器常用的两种实现方法

    方法一,使用线程中现成的: 这种一般比较常用,特别是在线程中的使用方法,下面是一个例子能够很清楚的说明它的具体使用方法: #! /usr/bin/python3 #! -*- conding: utf ...

  10. 关于C#中timer类 在C#里关于定时器类就有3个

    ·关于C#中timer类  在C#里关于定时器类就有3个   1.定义在System.Windows.Forms里   2.定义在System.Threading.Timer类里   3.定义在Sys ...

最新文章

  1. Linux 创建yum源和软件仓库实例
  2. 11组软件工程组队项目失物招领系统——进度汇报和下周目标
  3. 机器学习中的不平衡分类方法(part6)--支持向量机
  4. 【OS学习笔记】十二 现代处理器的结构和特点
  5. Linux已经霸占了服务器领域
  6. ucos ii 源代码中文注释详解 : OS_TIME.C
  7. 1.5 基础数据类型 -- 字典与集合
  8. 使用账户和密码在FTP客户端连接FTP服务器,出现vsftpd:500 OOPS: vsftpd: refusing to run with writable root inside chroot
  9. 这四行棘手的C代码背后的概念
  10. java 字符串 float_Java 字符串转float运算 float转字符串
  11. [python+pip] 使用pip将函数库安装到Python环境或Anaconda环境
  12. openssl生成CA证书
  13. 使用robo 3t连接mongodb的方法
  14. 考研数据库复习(一) 过一遍教材
  15. Contest3115 - 2021级新生个人训练赛第23场_问题 H: 家庭作业
  16. 计算机毕业设计SSM高校第二课堂管理系统【附源码数据库】
  17. mmdetection config文件中几个参数的理解(anchor_scales,anchor_ratios,anchor_strides)
  18. 深度学习的显卡对比评测:2080ti vs 3090 vs A100
  19. 如何移除Office 365标题栏上的账号信息
  20. 月入3万,个人博客的暴利赚钱套路分享!

热门文章

  1. nevada用计算机弹,Nevada吉他谱(gtp谱,指弹,独奏,演奏视频)_Vicetone
  2. 第二周工作总结——NWNU李泓毅
  3. 局域网内查询嵌入式设备IP的几种方式
  4. 2019 iPad iPhone所有尺寸
  5. 狂神说Java之Springboot整合Shiro
  6. JavaScript知识点全面概括与总结(上)
  7. Linux常用命令、相关软件安装及项目部署
  8. 关于TP3.2.3的反序列化学习
  9. 如何给grldr.mbr和grldr改名
  10. the specified jre installation does not exist 规定的jre没有安装