C#中 System.Threading.Timer 的回收问题

摘要:
当客户端打开时,计时器(System.Threading.timer)将同步一次。此外,程序在离开系统范围之前不会执行。线程。计时器。编译器将添加相关的方法属性(编译器将为程序集设置DebuggingModes的DisableOptimizations标志),以防止CLR垃圾收集器在离开作用域之前回收它。

一. 问题来源

在我上家公司里,做停车软件客户端的时候,岗亭客户端需要每隔一段时间,将本地时间和服务所在的电脑上的时间,和中央服务器上的本地时间进行同步。但是在实际运用的时候,打开客户端除了开启计时器(System.Threading.Timer)的时候会同步一次以外,之后就再也不会同步。

二. 关于 System.Threading.Timer

System.Threading.Timer 是一个比较特殊的对象,在程序还没有执行到离开 System.Threading.Timer 的作用域的时候。如果发生一次 GC 的回收,那么在 Release 编译的模式下,这个计时器会直接被当做垃圾而被 CLR 回收,造成无法正常进行定时操作。

当然在 Debug 编译模式下,并不会发生这个问题。因为在 Debug 模式下,编译器会添加相关的方法特性(编译器会为程序集设置 DebuggingModes 的 DisableOptimizations 标志)来阻止 CLR 垃圾回收器在离开作用域之前回收它,将所有根的生存周期延长至方法结束。

值得一提的是,只要有一个根在引用它,对于其他的对象,并不会造成在离开作用域之前就被回收。所以 System.Threading.Timer 需要我们在某些特殊情况下区别于其他对象进行对待。

三. 解决 System.Threading.Timer 被提前回收的方法

解决这个被提前回收的方法主要有一下几种:

1. 如果是在命令窗口中编译,使用 " /Debug+ " ,于此同时不要去启用 " /optimize "。如果是在VS中那么直接在上面的工具栏中选择 " Debug " 即可。当然这种方法是有缺点的,就是不能处理 Release 编译的程序;

操作步骤:

step 1:测试代码(已经在应用进行精简删除 仅仅保留命名空间 using System )

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Threading.Timer timer = new System.Threading.Timer(ShowCuurentDataTime, null, 0, 1000);

            //让timer不能离开当前作用域
            Console.ReadKey();
        }

        public static void ShowCuurentDataTime(Object obj)
        {
            Console.WriteLine("[System.Threading.Timer]当前时间:" + DateTime.Now);

            //强制GC回收
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        }
    }
}

step 2:命令窗口中编译

  • 首先打开 " VS20XX 开发人员命令提示 ",可以在开始菜单的 VS 目录下面找到,如下图(用这个的原因是,这样执行指令的时候,编辑环境由其准备好了),如下图:

C#中 System.Threading.Timer 的回收问题第1张

  • 然后打开之后,转到 program.cs 所在目录。然后执行命令
csc /t:exe /r:System.dll /Debug+ /optimize- program.cs

如下图:

C#中 System.Threading.Timer 的回收问题第2张

  • 之后在目录下面就会有一个exe生成,打开之后可以正常处理计时事件。

step 3:如果是在 Visual Studio IDE 下那么直接在工具栏上把 Debug 选上编译运行,在 bindebug 目录下的会有 exe ,如下图:

C#中 System.Threading.Timer 的回收问题第3张

注:

  • 当然要想 Releas 编译的程序能使定时器正常运作,这种方法是不行的;
  • 使用其他类型的计时器,例如 System.Timers.Timer 或者是 System.Windows.Forms.Timer 。前者比较好用,后者会占用 UI 线程,不建议处理长时间的同步业务,如果写成异步的,那么还是需要考虑异步的相关问题;
  • 在程序结束之前增加计时器的应用根。一种是在退出,或者释放承载了该种计时器的对象的时候,对计时器进行显式的引用释放,如
timer.Dispose();

值得注意的是,并不能使用

t=null;

这样来引用,因为编辑器会优化代码,而忽略这一行。还有一种方式就是把 Timer 设置为 Timer 承载对象的一个字段或者属性,让 Timer 的生命周期得以延长到承载对象的生命周期结束:

【1】在程序退出之前,对计时器进行显式的引用释放的方法,如下图:

C#中 System.Threading.Timer 的回收问题第4张

【2】使用字段或者属性,延长Timer生命周期,如下图:

C#中 System.Threading.Timer 的回收问题第5张 

四. 项目工程下载

下载地址

免责声明:文章转载自《C#中 System.Threading.Timer 的回收问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇阿里云内网和公网NTP服务器和其他互联网基础服务时间同步服务器ASP.NET Core 入门教程 9、ASP.NET Core 中间件(Middleware)入门下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

win10系统遇到的问题解决

  1.win10 计算器提示:需要新应用打开此calculator 运行calc,会出现需要新应用打开此Calculator,打开应用商店,找到计算器,仍然可以被使用,我怀疑是我自己在清理PC的注册表的时候将系统的一些设置修改了,导致c:WindowsSystem32calc这个程序与应用商店里面的计算器之间的对应关系没有了,这样每次运行calc的...

android:onTouch()和onTouchEvent()的区别?看完这篇文章就知道了

Android Touch Screen 与传统Click Touch Screen不同,会有一些手势(Gesture),例如Fling,Scroll等等。这些Gesture会使用户体验大大提升。 Android中的Gesture识别(detector)是通过GestureDetector.OnGestureListener接口实现的。 首先,Androi...

Navigation Bar的简单设置

前面的一篇文章《iOS开发16:使用Navigation Controller切换视图》中的小例子在运行时,屏幕上方出现的工具栏就是Navigation Bar,而所谓UINavigationItem就可以理解为Navigation Bar中的内容,通过编辑UINavigationItem,我们可以使得在Navigation Bar中显示想要的东西,比如设置...

Object非空判断

类Objects,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),那么在它的源码中,对对象为null的值进行了抛出异常操作。 public static T requireNonNull(T obj) :查看指定引用对象不是null。 查看源码发现这里对为null的进行了抛出异常操作:...

C# Thread线程介绍

一、基本概念 1、进程 首先打开任务管理器,查看当前运行的进程: 从任务管理器里面可以看到当前所有正在运行的进程。那么究竟什么是进程呢? 进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程...

java中远程http文件上传及file2multipartfile

    工作中有时会遇到各种需求,你得变着法儿去解决,当然重要的是在什么场景中去完成。 比如Strut2中file类型如何转换成multipartfile类型,找了几天,发现一个变通的方法记录如下(虽然最后没有用上。。): 1 private static MultipartFile getMulFileByPath(String picPath) {...