zoukankan      html  css  js  c++  java
  • 几个常见的定时器

    NET允许不同的命名空间里存在同名的类——“System.Timers.Timer, System.Threading.Timer和Sytem.Windows.Forms.Timer”就是一个很好的例子。那么它们之间有何区别呢?我们这就来分析一下:

    [1]System.Windows.Forms.Timer:这个Timer是我们最最常见的一个Timer,主要用于一般Windows窗体编程时候的定时使用。(主要的属性:Interval——用于控制每隔多少时间出发一次Tick事件,Enabled——是否立即启动定时器,Tick事件——用于在Interval到时之后触发的行为)。

    由于该Timer是相对整个WinForm(UI)上的异步操作,因此可以直接控制WinForm上的任何控件;不过缺陷在于既然是基于UI界面的主线程的异步操作,这将导致如果Timer执行一个长时间的任务,会导致界面死亡,这也就证明了Timer并非是我们一些人所谓的“多线程”(注意:笔者在这里“多线程”指代并行运行的线程,或者不会引发UI死掉的线程)。我们可以尝试这个代码(在Tick事件中):

    [C#]

    Thread.Sleep(10000);

    [VB.NET]

    Thread.Sleep(10000)

    启动定时器之后,发现界面会假死10秒左右的时间。另外时间不一定精准(因为基于UI异步,或许会有延时等问题)。

    [2]System.Threading.Timer:

    该Timer是一个基于ThreadPool构建的Timer,相对第一个而言最大的优势在于不占用UI线程,也就是真正的“并行”多线程;再者由于是使用ThreadPool,自然其内部通过线程池管理线程,可以达到资源的最大化。不过通过MSDN上我们注意到一个“劣势”:

    System.Threading.Timer 是一个简单的轻量计时器,它使用回调方法并由线程池线程提供服务。 不建议将其用于 Windows 窗体,因为其回调不在用户界面线程上进行。 System.Windows.Forms.Timer 是用于 Windows 窗体的更佳选择。

    这句话告诉读者说第二个Timer(也就是我现在这段说的Timer)不适合用于WinForm,所以建议读者使用第一种Timer。我对这句话有些“嗤之以鼻”的(虽然我承认绝大部分MSDN的确相当不错!)——究其原因,是因为既然该Timer基于ThreadPool,如果操作了WinForm的Control,在调试的时候会抛出“试图从非创建该控件的线程中去访问该控件”的异常(这个问题我的以前一篇博文涉及到过)。我们为何不能使用控件的Invoke呢?改造一下:

    [C#]

    public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                System.Threading.Timer t = new System.Threading.Timer
                    (new System.Threading.TimerCallback((obj) =>
                    {
                        this.Invoke(new MethodInvoker(() => { label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); }));
                    }), null, 0, 1000);
            }
        }

    [VB.NET]

    Public Partial Class Form1
        Inherits Form
        Public Sub New()
            InitializeComponent()
        End Sub
    
        Private Sub button1_Click(sender As Object, e As EventArgs)
            Dim t As New System.Threading.Timer(New System.Threading.TimerCallback(Function(obj) 
            Me.Invoke(New MethodInvoker(Function() 
            label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
    End Function))
    End Function), Nothing, 0, 1000)
        End Sub
    End Class

    之所以用Me.Invoke,这是为了避免用非创建该控件的线程去调用该控件所引发的错误。

    另外该Timer没有Start什么的启动方法,初始化之后就自动启动,其最原始的构造函数如下:

    [C#]

    public Timer(
        TimerCallback callback,
        Object state,
        int dueTime,
        int period
    )

    [VB.NET]

    Public Sub New ( _
        callback As TimerCallback, _
        state As Object, _
        dueTime As Integer, _
        period As Integer _
    )

    其中CallBack是构造函数(回调函数,相当于Tick事件),state是需要操作的对象(一般为空,不需要),dueTime:隔多少毫秒之后第一次触发回调函数(>0,如果=0,初始化后立即回调函数执行,<0,停止),period:每隔多少时间执行重复执行回调函数(>0,如果<=0,只执行一次回调函数,如果dueTime>0的话)。

    [3]System.Timers.Timer:

    这个类一般用于服务端(譬如Windows服务等),最大的特色在于它“集成”了1和2的特点——

    3.1)基于ThreadPool:因此和[2]的那个Timer一样,必须用Invoke的方法方可在WinForm中使用。示例代码如下:

    [C#]

    public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                System.Timers.Timer t = new System.Timers.Timer(1000);
                t.Elapsed += t_Elapsed;
                t.AutoReset = true;       
                t.Start();
            }
    
            void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
            {
                if (!this.InvokeRequired)
                {
                    label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                }
                else
                {
                    this.Invoke(new MethodInvoker(() => { label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); }));
                }
            }
        }

    [VB.NET]

    Public Partial Class Form1
        Inherits Form
        Public Sub New()
            InitializeComponent()
        End Sub
    
        Private Sub button1_Click(sender As Object, e As EventArgs)
            Dim t As New System.Timers.Timer(1000)
            AddHandler t.Elapsed, AddressOf t_Elapsed
            t.AutoReset = True
            t.Start()
        End Sub
    
        Private Sub t_Elapsed(sender As Object, e As System.Timers.ElapsedEventArgs)
            If Not Me.InvokeRequired Then
                label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
            Else
                Me.Invoke(New MethodInvoker(Function() 
                label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
    
    End Function))
            End If
        End Sub
    End Class

    3.2)有可视化的界面,但是默认不会加载到控件库中,必须手动添加方可:

    这里大家请注意一点——一旦我们使用该可视化的System.Timers.Timer,其实效果是和[1]的那个几乎一样的,失去了ThreadPool的优势。究其原因在于你把该控件拖拽到了WinForm上,后台代码会在上面类似3.1中代码中额外地增加一条“t.SynchronizingObject = this;”(VB.NET: t.SynchronizingObject = Me),这个"SynchronizingObject"默认是null,表示从线程池引发间隔的时间行为;但是如果设置成了this(窗体),则变成了从窗体的UI上每隔一定时间触发一次事件行为。看反射后的代码更有助于我们了解内部状况(有删节):

    [C#]

    [DefaultEvent("Elapsed"), DefaultProperty("Interval"), HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
    public class Timer : Component, ISupportInitialize
    {
        // Fields
        private bool autoReset;
        private TimerCallback callback;
        private object cookie;
        private bool delayedEnable;
        private bool disposed;
        private bool enabled;
        private bool initializing;
        private double interval;
        private ISynchronizeInvoke synchronizingObject;
        private Timer timer;
    
        // Events
        [Category("Behavior"), TimersDescription("TimerIntervalElapsed")]
        public event ElapsedEventHandler Elapsed;
    
        // Methods
        public Timer()
        {
            this.interval = 100.0;
            this.enabled = false;
            this.autoReset = true;
            this.initializing = false;
            this.delayedEnable = false;
            this.callback = new TimerCallback(this.MyTimerCallback);
        }
    
        public Timer(double interval) : this()
        {
            if (interval <= 0.0)
            {
                throw new ArgumentException(SR.GetString("InvalidParameter", new object[] { "interval", interval }));
            }
            double num = Math.Ceiling(interval);
            if ((num > 2147483647.0) || (num <= 0.0))
            {
                throw new ArgumentException(SR.GetString("InvalidParameter", new object[] { "interval", interval }));
            }
            this.interval = (int) num;
        }
    
      
        private void MyTimerCallback(object state)
        {
            if (state == this.cookie)
            {
                if (!this.autoReset)
                {
                    this.enabled = false;
                }
                FILE_TIME lpSystemTimeAsFileTime = new FILE_TIME();
                GetSystemTimeAsFileTime(ref lpSystemTimeAsFileTime);
                ElapsedEventArgs e = new ElapsedEventArgs(lpSystemTimeAsFileTime.ftTimeLow, lpSystemTimeAsFileTime.ftTimeHigh);
                try
                {
                    ElapsedEventHandler onIntervalElapsed = this.onIntervalElapsed;
                    if (onIntervalElapsed != null)
                    {
                        if ((this.SynchronizingObject != null) && this.SynchronizingObject.InvokeRequired)
                        {
                            this.SynchronizingObject.BeginInvoke(onIntervalElapsed, new object[] { this, e });
                        }
                        else
                        {
                            onIntervalElapsed(this, e);
                        }
                    }
                }
                catch
                {
                }
            }
        }
    
     ………………
    }

    [VB.NET]

    <DefaultEvent("Elapsed"), DefaultProperty("Interval"), HostProtection(SecurityAction.LinkDemand, Synchronization := True, ExternalThreading := True)> _
    Public Class Timer
        Inherits Component
        Implements ISupportInitialize
        ' Fields
        Private autoReset As Boolean
        Private callback As TimerCallback
        Private cookie As Object
        Private delayedEnable As Boolean
        Private disposed As Boolean
        Private enabled As Boolean
        Private initializing As Boolean
        Private interval As Double
        Private synchronizingObject As ISynchronizeInvoke
        Private timer As Timer
    
        ' Events
        <Category("Behavior"), TimersDescription("TimerIntervalElapsed")> _
        Public Event Elapsed As ElapsedEventHandler
    
        ' Methods
        Public Sub New()
            Me.interval = 100.0
            Me.enabled = False
            Me.autoReset = True
            Me.initializing = False
            Me.delayedEnable = False
            Me.callback = New TimerCallback(AddressOf Me.MyTimerCallback)
        End Sub
    
        Public Sub New(interval As Double)
            Me.New()
            If interval <= 0.0 Then
                Throw New ArgumentException(SR.GetString("InvalidParameter", New Object() {"interval", interval}))
            End If
            Dim num As Double = Math.Ceiling(interval)
            If (num > 2147483647.0) OrElse (num <= 0.0) Then
                Throw New ArgumentException(SR.GetString("InvalidParameter", New Object() {"interval", interval}))
            End If
            Me.interval = CInt(Math.Truncate(num))
        End Sub
    
    
        Private Sub MyTimerCallback(state As Object)
            If state Is Me.cookie Then
                If Not Me.autoReset Then
                    Me.enabled = False
                End If
                Dim lpSystemTimeAsFileTime As New FILE_TIME()
                GetSystemTimeAsFileTime(lpSystemTimeAsFileTime)
                Dim e As New ElapsedEventArgs(lpSystemTimeAsFileTime.ftTimeLow, lpSystemTimeAsFileTime.ftTimeHigh)
                Try
                    Dim onIntervalElapsed As ElapsedEventHandler = Me.onIntervalElapsed
                    If onIntervalElapsed IsNot Nothing Then
                        If (Me.SynchronizingObject IsNot Nothing) AndAlso Me.SynchronizingObject.InvokeRequired Then
                            Me.SynchronizingObject.BeginInvoke(onIntervalElapsed, New Object() {Me, e})
                        Else
                            onIntervalElapsed(Me, e)
                        End If
                    End If
                Catch
                End Try
            End If
        End Sub
    End Class

    请注意——无论你使用何种方式初始化第三个类型的Timer,必然会初始化TimerBack这个委托——此时其默认会绑定到其内部的一个私有函数中去判断究竟用何种方式进行计时:如果SynchronizingObject不是null(这里假设也就是某个窗体的实例)的话,那么等于异步调用它的BeginInvoke方法——如果你的任务非常复杂,也就很可能导致WinForm宕掉。所以一般而言如果在WinForm使用这个Timer,我们一般还是手动初始化而不是直接拖拽控件

    相比较而言,该控件还有一个最大的好处——它构造函数允许一个double类型的时间间隔,这就意味着可以传入1.7976931348623157E+308毫秒的时间间隔,一般地,有时服务器定时完成的任务可能是一段很久的时间(比如一个月啥的)。因此这个类当之无愧适用于服务器,是其最佳优选对象。

  • 相关阅读:
    listview 优化
    重要博客网址
    bottombar——Fragment
    视频播放,,今日头条样式
    databinding
    Picasso
    22222222
    202004leetcode刷题记录
    批量下载邮箱中指定日期范围的附件
    有雾环境下的目标检测
  • 原文地址:https://www.cnblogs.com/ServiceboyNew/p/2732680.html
Copyright © 2011-2022 走看看