.NET的内建定时器类型是否会发生回调方法冲入

分析问题

  所谓的方法重入,是一个有关多线程编程的概念。程序中多个线程同时运行时,就可能发生同一个方法被多个线程同时调用的情况。当这个方法中存在一些非线程安全的代码时,方法重入就会导致数据不一致的情况,这是非常严重的Bug。

  在前文中,笔者已经简要介绍了.NET的内建定时器类型,它们是:

  1、System.Windows.Forms.Timer。

  2、System.Threading.Timer。

  3、System.Timers.Timer。

  这三种类型的计时方法是不同的,这里笔者分别分析了三种类型是否会存在方法重入的情况。

  1、System.Windows.Forms.Timer类型。

  在前文中笔者已经介绍了,System.Windows.Forms.Timer类型的计时机制实在当前UI线程的消息队列里插入一条定时消息,这样的机制保证了不破坏单线程的运行环境。在这种情况下,计时定时器的时间间隔被设置的最小,后一个定时消息必须等待前一个消息处理完毕。所以在这种情况下是不会发生回调方法重入的情况的。

  2、System.Threading.Timer的回调方法在一个工作者线程上执行,每当一个定时事件发生时,控制System.Threading.Timer对象的线程就会负责从线程池中分配一个新的工作者线程,这是一种典型的多线程编程环境,所以方法重入的现象是可能发生的。这就需要程序员在编写System.Threading.Timer类型对象的回调方法时,注意线程同步的问题。

  3、System.Timers.Timer类型。

  System.Timers.Timer类型可以看作System.Threading.Timer的一个封装类型,其可以通过同步块设置属性,这个时候,其特性和System.Windows.Forms.Timer非常类似,并且不会发生回调方法重入的情况。当当其同步快属性未设定时,它的回调方法就会在一个工作者线程上被执行,这时候,它的回调方法就可能产生重入的情况。

  以下代码展示了System.Threading.Timer类型和System.Timers.Timer类型的重入情况。

using System;

namespace Test
{
    class Reenter
    {
        //用来造成线程同步问题的静态成员
        private static int TestInt1 = 0;
        private static int TestInt2 = 0;

        static void Main()
        {
            Console.WriteLine("System.Timers.Timer回调方法重入测试:");
            TimersTimerReenter();
            //这里确保已经开始的回调方法有机会结束
            System.Threading.Thread.Sleep(2000);
            Console.WriteLine("System.Threading.Timer回调方法重入测试:");
            ThreadingTimerReenter();
            Console.Read();
        }

        /// <summary>
        /// 展示System.Timers.Timer的回调方法重入
        /// </summary>
        static void TimersTimerReenter()
        {
            System.Timers.Timer timer = new System.Timers.Timer();
            timer.Interval = 100;//100毫秒
            timer.Elapsed += TimersTimerHandler;
            timer.Start();
            System.Threading.Thread.Sleep(2000);//运行2秒
            timer.Stop();
        }

        /// <summary>
        /// 展示System.Threading.Timer的回调方法重入
        /// </summary>
        static void ThreadingTimerReenter()
        {
            using (System.Threading.Timer timer=new System.Threading.Timer(new System.Threading.TimerCallback (ThreadingTimerHandler),null,0,100))
            {
                System.Threading.Thread.Sleep(2000);//运行2秒
            }
        }

        static void ThreadingTimerHandler(object state)
        {
            Console.WriteLine("测试整数:{0}",TestInt2.ToString());
            //睡眠10s,保证方法重入
            System.Threading.Thread.Sleep(10000);
            TestInt2++;
            Console.WriteLine("自增1后测试整数:{0}",TestInt2.ToString());
        }

        /// <summary>
        /// System.Timers.Timer的回调方法
        /// </summary>
        static void TimersTimerHandler(object sender, EventArgs e)
        {
            Console.WriteLine("测试整数:{0}",TestInt1.ToString());
            //睡眠10s,保证方法重入
            System.Threading.Thread.Sleep(10000);
            TestInt1++;
            Console.WriteLine("自增1后测试整数:{0}",TestInt1.ToString());
        }

    }
}

  在以上代码中,为了保证定时器回调方法的执行时间长于定时器的间隔时间,添加了让线程睡眠1s的代码:

System.Threading.Thread.Sleep(1000);

  在这种情况下,输出将和预期的有很大不同,多个回调方法将并行地执行并且无法控制其顺序:

  

  

  正如输出所显示的,所有回调方法并行执行的结果是执行顺序杂乱无章,并且操作的全局变量可能会在其他线程中被修改。为了避免发生这种情况,程序员需要为回调方法添加lock锁,下面的代码展示了这一做法:

private static object lockObj = new object();

        /// <summary>
        /// System.Threading.Timer的回调方法
        /// </summary>
        /// <param name="state"></param>
        static void ThreadingTimerHandler(object state)
        {
            lock (lockObj)
            {
                Console.WriteLine("测试整数:{0}", TestInt2.ToString());
                //睡眠10s,保证方法重入
                System.Threading.Thread.Sleep(10000);
                TestInt2++;
                Console.WriteLine("自增1后测试整数:{0}", TestInt2.ToString());
            }
        }

        /// <summary>
        /// System.Timers.Timer的回调方法
        /// </summary>
        static void TimersTimerHandler(object sender, EventArgs e)
        {
            lock (lockObj)
            {
                Console.WriteLine("测试整数:{0}", TestInt1.ToString());
                //睡眠10s,保证方法重入
                System.Threading.Thread.Sleep(10000);
                TestInt1++;
                Console.WriteLine("自增1后测试整数:{0}", TestInt1.ToString());
            }
        }

  在加了同步锁的情况下,可以保证所有时间只有一个线程可以执行回调方法,而其他线程将会被迫阻塞等待,这是加锁后的输出:

  如读者看到的,加锁后的输出是有规律的,线程同步的问题得到了解决,但是运行程序的时候读者也可能已经感觉到了,加锁本质上破获了多线程并行优势,使得程序的执行变得相对缓慢。所以程序员在编写定时器代码时,应仔细考虑何时需要加锁,而合适需要确保多线程并行运行。

答案

  在.NET的内建定时器中,System.Timers.Timer和System.Threading.Timer两个类型可能发生回调方法重入的问题,而System.Windows.Forms.Timer则不存在这个问题。

  在定时器设计时,需要考虑是否需要为回调方法加锁和如何加锁,原则上被加锁的代码越少,则对效率的影响也越小。    

  

时间: 2024-08-29 20:14:35

.NET的内建定时器类型是否会发生回调方法冲入的相关文章

golang内建变量类型

1. bool,string 2.(u)int, (u)int8, (u)int16, (uint)32, (u)int64, uintptr (1)uintptr 是指针类型 3. byte(8位), rune(go语言的字符型,32位)  一个字节的char  都是整数类型的别名 4. float32, float64, complex64, complex128 (1)complex是负数 5. 强制类型转换 (1)golang只有强制类型转换,没有隐式类型转换 //需要強制顯示轉換類型

go_内建变量类型

bool, string (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr (uintptr 是指针) byte, rune(表示字符char) float32, float64, complex64, complex128 (complex表示复数) go语言只能强制类型转换,不能隐式转化 package main import ( "fmt" "math/cmplx" "math"

[golang note] 内建类型

基础类型 √ golang内建基础类型有布尔类型.整数类型.浮点类型.复数类型.字符串类型.字符类型和错误类型. 复合类型 √ golang支持的复合类型有指针.数组.数组切片.字典.通道.结构体和接口.

go基础语法-内置变量类型

内建变量类型 1.内建变量一览 bool,string (u)int,(u)int8,(u)int16,(u)int32,(u)int64,uintptr 无长度int的实际长度取决于操作系统位数(32/64) uintptr为指针类型 byte,rune rune相当于其他语言的char,长度为int4(32位) float32,float32,complex64,complex128 complex为复数(1+1i) 2.强制类型转换 golang有严格的类型匹配,不同的类型之间通常需要手动

python高级编程之(类级):子类内建类型

# -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #类级 #在2.2中,提出了类型(type0与类(class)统一(请访问:https://www.python.org/download/releases/2.2.3/descintro(可能已经不存在了))-这使内建类型的子类化成为可能,并且添加一个新内建类型object #用于所有内建类的公共祖先 #展示一个名为distinctdict类的代码,与平常的dic

零基础学python-4.2 其它内建类型

这一章节我们来聊聊其它内建类型 1.类型type 在python2.2的时候,type是通过字符串实现的,再后来才把类型和类统一 我们再次使用上一章节的图片来说明一些问题 我们通过对照上面的图片.在python3.4的时候,type有了一些改变,从2.7的返回<type 'xxx'>到3.4的返回<class 'xxx'>.除了返回一个类之外,还把提示给改了.使用class.这样更加清晰,明白 2.Null(None) 3.文件(在其它章节介绍) 4.集合(在其它章节介绍) 5.函

python基础(文件输入/输出 内建类型 字典操作使用方法)

本文主要介绍了python基础入门,包括文件输入/输出.内建类型.字典操作等使用方法 一.变量和表达式 代码如下: >>> 1 + 1 2>>> print 'hello world' hello world>>> x = 1               >>> y = 2>>> x + y3 Python是强类型语言,无法根据上下文自动解析转换成合适的类型. Python是一种动态语言,在程序运行过程中,同一个变量

【ThinkingInC++】43、内建类型封装在一个类里

内建类型 C++数据类型包括: 1)内置的基本数据类型,如整型.浮点型.布尔型等,均有一个关键字对应,如int,float,bool 2)C++ STL(标准库)引入了一些扩展类型,有时候也归为基本类型,比如字符串类型(string),复数类型(complex),向量(vector)等. 3)其他,即用户自定义类型,也叫抽象数据类型(ADT),即用户通过class,struct,enum定义的各种数据类型. /** * 书本:[ThinkingInC++] * 功能:把一个内建类型封装在一个类里

16 集合类型内建方法总结 (转)

集合类型内建方法总结 集合(s).方法名 等价符号 方法说明 s.issubset(t) s <= t 子集测试(允许不严格意义上的子集):s 中所有的元素都是 t 的成员   s < t 子集测试(严格意义上):s != t 而且 s 中所有的元素都是 t 的成员 s.issuperset(t) s >= t 超集测试(允许不严格意义上的超集):t 中所有的元素都是 s 的成员   s > t 超集测试(严格意义上):s != t 而且 t 中所有的元素都是 s 的成员 s.un