zoukankan      html  css  js  c++  java
  • [经验栈]C#中几种定时器(timer)的区别

    1、前言

    ​ 不知道你是否对.NET里面的定时器产生过一些疑问,以下是武小栈个人的一些总结。

    2、官方介绍

    在.NET的框架之内定时器有四种,先看一下微软官方对他们各自特点介绍:

    • System.Timers.Timer,它将触发事件,并定期在一个或多个事件接收器中执行代码。 类旨在用作多线程环境中基于服务器的组件或服务组件;它没有用户界面,在运行时不可见。
    • System.Threading.Timer,它按固定的时间间隔对线程池线程执行单个回调方法。 回调方法是在实例化计时器时定义的,无法更改。 与 System.Timers.Timer 类一样,此类用作多线程环境中基于服务器的或服务组件;它没有用户界面,在运行时不可见。
    • System.Windows.Forms.Timer (仅 .NET Framework),这是一个触发事件并定期在一个或多个事件接收器中执行代码的 Windows 窗体组件。 组件没有用户界面,旨在在单线程环境中使用;它在 UI 线程上执行。
    • System.Web.UI.Timer (仅 .NET Framework),是一种定期执行异步或同步网页回发的 ASP.NET 组件。

    再看看微软对开发者的使用建议:

    System.Threading.Timer 是一种简单的轻型计时器,它使用回调方法,并由线程池线程提供服务。 不建议与 Windows 窗体一起使用,因为它的回调不会在用户界面线程上发生。 System.Windows.Forms.Timer 是用于 Windows 窗体的更好选择。 对于基于服务器的计时器功能,您可以考虑使用 System.Timers.Timer,这会引发事件并具有其他功能。

    3、个人体会

    System.Threading.Timer Class

    是一个基础类,使用起来不是太好用,各种用法较为原始,用的较少。

    System.Windows.Forms.Timer Class

    第一次接触的就是它,毕竟直接winform拖下来就行了,用的还是比较多,我通常用在运行一些刷新界面的代码,这些代码通常不会有什么逻辑运算,比如界面上需要显示一个倒计时。

    在这个类使用中我遇到过两个疑惑,作为分享:

    Q1:Tick实践会创建新线程执行吗?

    A1:不会创建新的线程,始终在主线程里面运行Tick事件;

    Q2:定时器会start()瞬间触发一次,还是等待Interval间隔后再触发?

    A2:等待Interval间隔后再触发。

    Q3:定时器start()和stop()时候Interval会累积吗?

    A3:不累积,每次start()重新计时。

    Q4:如果Tick事件内的代码未执行完成,但是下一次Tick定时已经达到会发生什么?

    A4:不会强行终止未完成的代码,也不会因为上一次Tick事件代码未执行完成而不再触发,而是类似于栈的形式将之前未执行完成的代码堆积,后触发的Tick事件内的代码先执行,先触发未完成的代码后执行,具体可以看下面示例。

        public Form1()
        {
            InitializeComponent();
            timerForm.Tick += TimerForm_Tick;
        }
    
        private int num = 1;//一个序号,表示当前第几次进入Tick事件
    
        private int rowNum = 1;//一个全局的行号,记录一下总共AppendText多少次
    
        private void TimerForm_Tick(object sender, EventArgs e)
        {
            
            string s = $"我是第{num++}次";
            for (int i = 0; i < 5; i++)
            {
                textBox1.AppendText($"{rowNum++}  {s}  序号i={i}  当前线程ID={Thread.CurrentThread.ManagedThreadId.ToString()} 
    ");
                Delay(1000);
            }
        }
        private Timer timerForm = new Timer(){Interval = 1000};
        private void button1_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("button  " + Thread.CurrentThread.ManagedThreadId.ToString() + "
    ");
            timerForm.Start();
        }
        public static void Delay(int mimillisecond)
        {
            int start = Environment.TickCount;
            while (Math.Abs(Environment.TickCount - start) < mimillisecond)
            {
                System.Windows.Forms.Application.DoEvents();
            }
        }
    

    FormTimer

    System.Timers.Timer Class

    ​ 是对System.Threading.Timer的一层封装,都是通过委托方法TimerCallback进行回调触发定时器事件,可以先看看System.Timers.Timer的代码实现方式:

          if (!value)
          {
            if (this.timer != null)
            {
              this.cookie = (object) null;
              this.timer.Dispose();
              this.timer = (System.Threading.Timer) null;
            }
            this.enabled = value;
          }
          else
          {
            this.enabled = value;
            if (this.timer == null)
            {
              if (this.disposed)
                throw new ObjectDisposedException(this.GetType().Name);
              int dueTime = (int) Math.Ceiling(this.interval);
              this.cookie = new object();
              this.timer = new System.Threading.Timer(this.callback, this.cookie, dueTime, this.autoReset ? dueTime : -1);
            }
            else
              this.UpdateTimer();
          }
    

    不过 System.Threading.Timer的属性和方法都更加友善,我通常在使用中不设计更新界面,都会使用这个定时器类,有一点要说明的是,将SynchronizingObject属性赋值到控件后,事件中代码会在控件上委托调用,如timer.SynchronizingObject = this;可以看下System.Timers.Timer内部是如何实现的。

    if (elapsedEventHandler != null)
            {
              if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
              {
                this.SynchronizingObject.BeginInvoke(elapsedEventHandler, new object[]
                {
                  this,
                  elapsedEventArgs
                });
              }
              else
              {
                elapsedEventHandler(this, elapsedEventArgs);
              }
            }
    

    ​ 虽然System.Timers.Timer定时器理论上是不受单线程限制,可以短时间内触发多次,但是实际上会受到线程池的限制,先看巨硬对于此的说明:

    如果 nullSynchronizingObject 属性,则在 ThreadPool 线程上引发 Elapsed 事件。 如果 Elapsed 事件的处理持续时间超过 Interval,则可能会在其他 ThreadPool 线程上再次引发该事件。 在这种情况下,事件处理程序应该是可重入的。

    1、当SynchronizingObject不为null,将在指定的对象线程上触发事件,为单线程触发,与System.Windows.Forms.Timer执行方式相同;

    2、当SynchronizingObject不为null时将在线程池(ThreadPool)上引发事件,执行事件内的代码。理论上可以重复载入,但是会受到ThreadPool线程数限制,比如ThreadPool.SetMaxThreads(8, 8),那么定时器触发事件只能同时载入8次;

    4、后记

    我现在用定时器基本上都是用System.Timers.Timer,在我看来System.Timers.Timer可以用SynchronizingObject属性实现在主线程运行,也可以不设置SynchronizingObject属性,是事件在线程池里触发,作为后台线程使用,基本能满足我在开发中的使用需求。

    参考资料

    System.Timers Namespace

    System.Windows.Forms

    System.Threading.ThreadPool Class

  • 相关阅读:
    import 和 from … import 模块的变量、方法引用差异
    python引入模块的五种方式与内置模块
    webdriver定位元素的方法和基础函数的使用
    mysql update语句 in执行效率优化
    服务器配置jupyter notebook
    安装CUDA和cuDNN
    Linux命令后台运行
    Ubuntu查看系统信息(CPU、GPU信息)
    Linux下scp用法简析
    如何解决“This app is damaged and can’t be opened. You should move it to the Trash”
  • 原文地址:https://www.cnblogs.com/wulinn/p/13292956.html
Copyright © 2011-2022 走看看