C# Timer讲解

再C#里如今有3个Timer类:安全

  • System.Windows.Forms.Timer
  • System.Threading.Timer
  • System.Timers.Timer

这三个Timer我想你们对System.Windows.Forms.Timer已经很熟悉了,惟一我要说的就是这个Timer在激发Timer.Tick事件的时候,事件的处理函数是在程序主线程上执行的,因此在WinForm上面用这个Timer很方便,由于在From上的全部控件都是在程序主线程上建立的,那么在Tick的处理函数中能够对Form上的全部控件进行操做,不会形成WinForm控件的线程安全问题。异步

Timer运行的核心都是System.Threading.ThreadPool

在这里要提到ThreadPool(线程池)是由于,System.Threading.Timer 和System.Timers.Timer运行的核心都是线程池,Timer每到间隔时间后就会激发响应事件,所以要申请线程来执行对应的响应函数,Timer将获取线程的工做都交给了线程池来管理,每到必定的时间后它就去告诉线程池:“我如今激发了个事件要运行对应的响应函数,麻烦你给我向操做系统要个线程,申请交给你了,线程分配下来了你就运行我给你的响应函数,没分配下来先让响应函数在这儿排队(操做系统线程等待队列)”,消息已经传递给线程池了,Timer也就无论了,由于它还有其余的事要作(每隔一段时间它又要激发事件),至于提交的请求何时可以获得知足,要看线程池当前的状态:函数

  • 一、若是线程池如今有线程可用,那么申请立刻就能够获得知足,有线程可用又能够分为两种状况:
    • <1>线程池如今有空闲线程,如今立刻就能够用
    • <2>线程池原本如今没有线程了,可是恰好申请到达的时候,有线程运行完毕释放了,那么申请就能够用别人释放的线程。
    • 这两种状况状况就如同你去游乐园玩赛车,若是游乐园有10辆车,如今有3我的在玩,那么还剩7辆车,你去了固然能够选一辆开。另外还有一种状况就是你到达游乐园前10辆车都在开,可是你运气很好,刚到游乐园就有人不玩了,正好你坐上去就能够接着开。
  • 二、若是如今线程池如今没有线程可用,也分为两种状况:
    • <1>线程池现有线程数没有达到设置的最大工做线程数,那么隔半秒钟.net framework就会向操做系统申请一个新的线程(为避免向线程分配没必要要的堆栈空间,线程池按照必定的时间间隔建立新的空闲线程。该时间间隔目前为半秒,但它在 .NET Framework 的之后版本中可能会更改)。
    • <2>线程池现有工做线程数达到了设置的最大工做线程数,那么申请只有在等待队列一直等下去,直到有线程执行完任务后被释放。

 那么上面提到了线程池有最大工做线程数,其实还有最小空闲线程数,那么这两个关键字是什么意思呢:性能

  • 一、最大工做线程数:实际上就是指的线程池可以向操做系统申请的最大线程数,这个值在.net framework中有默认值,这个默认值是根据你计算机的配置来的,当人你能够用ThreadPool.GetMaxThreads返回线程池当前最大工做线程数,你也能够同ThreadPool.SetMaxThreads设置线程池当前最大工做线程数。
  • 二、最小空闲线程数:是指在程序开始后,线程池就默认向操做系统要最小空闲线程数个线程,另外这也是线程池维护的空闲线程数(若是线程池最小空闲线程数为3,当前由于一些线程执行完任务被释放,线程池如今实际上有10个空闲线程,那么线程池会让操做系统释放多余的7个线程,而只维持3个空闲线程供程序使用),由于上面说了,在执行程序的时候在要线程池申请线程有半秒的延迟时间,这也会影响程序的性能,因此把握好这个值很重要,用样你能够用ThreadPool.GetMinThreads返回线程池当前最小空闲线程数,你也能够同ThreadPool.SetMinThreads设置线程池当前最小空闲线程数。

下面是我给的例子,这个例子让线程池申请800个线程,其中设置最大工做线程数为500,800个线程任务每一个都要执行100000000毫秒目的是让线程不会释放,而且让用户选择,是否预先申请500个空闲线程免受那半秒钟的延迟时间,其结果可想而知当线程申请到500的时候,线程池达到了最大工做线程数,剩余的300个申请进入漫长的等待时间:测试

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->
/***************************************************
 * 项目:测试线程池
 * 描述:验证线程池的最大工做线程数和最小空闲线程数
 * 做者:@PowerCoder
 * 日期:2010-2-22
***************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static int i=1;
        static int MaxThreadCount = 800;

        static void OutPut(object obj)
        {
            Console.Write("\r申请了:{0}个工做线程",i);
            i++;
            Thread.Sleep(100000000);//设置一个很大的等待时间,让每一个申请的线程都一直执行
        }

        static void Main(string[] args)
        {
            int j;
            
            Console.Write("是否先申请500个空闲线程以保证前500个线程在线程池中开始就有线程用(Y/N)?");///若是这里选择N,那么前两个任务是用的线程池
///默认空闲线程(能够用ThreadPool.GetMinThreads获得系统默认最小空闲线程数为2)申请当即获得知足,
///然而因为每一个线程等待时间很是大都不会释放当前本身持有的线程,所以线程池中已无空闲线程所用,
///后面的任务须要在线程池中申请新的线程,那么新申请的每一个线程在线程池中都要隔半秒左右的时间才能获得申请(缘由请见下面的注释)
            string key = Console.ReadLine();
            if(key.ToLower()=="y")
                ThreadPool.SetMinThreads(500, 10);///设置最大空闲线程为500,就好像我告诉系统给我预先准备500个线程我来了就直接用,
///由于这样就不用现去申请了,在线程池中每申请一个新的线程.NET Framework 会安排一个间隔时间,目前是半秒,之后的版本MS有可能会改
            
            int a, b;
            ThreadPool.GetMaxThreads(out a,out b);
            Console.WriteLine("线程池默认最大工做线程数:" + a.ToString() + "     默认最大异步 I/O 线程数:" + b.ToString());
            Console.WriteLine("须要向系统申请" + MaxThreadCount.ToString()+"个工做线程");

            for (j = 0; j <= MaxThreadCount-1; j++)///因为ThreadPool.GetMaxThreads返回的默认最大工做线程数为500(这个值要根据你计算机的配置来决定),
///那么向线程池申请大于500个线程的时候,500以后的线程会进入线程池的等待队列,等待前面500个线程某个线程执行完后来唤醒等待队列的某个线程 
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(OutPut));
                Thread.Sleep(10);
            }

            Console.ReadLine();
        }
    }
}

System.Threading.Timer

谈完了线程池,就能够开始讨论Timer,这里咱们先从System.Threading.Timer开始,System.Threading.Timer的做用就是每到间隔时间后激发响应事件并执行相应函数,执行响应函数要向线程池申请线程,固然申请中会遇到一些状况在上面咱们已经说了。值得注意的一点就是System.Threading.Timer在建立对象后当即开始执行,好比System.Threading.Timer timer = new System.Threading.Timer(Excute, null, 0, 10);这句执行完后每隔10毫秒就执行Excute函数不须要启动什么的。下面就举个例子,我先把代码贴出来:
spa

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class UnSafeTimer
    {
        static int i = 0;
        static System.Threading.Timer timer;
        static object mylock = new object();
        static int sleep;
        static bool flag;
        public static Stopwatch sw = new Stopwatch();

        static void Excute(object obj)
        {
            Thread.CurrentThread.IsBackground = false;
            int c;

            lock (mylock)
            {
                i++;
                c = i;
            }

            if (c == 80)
            {
                timer.Dispose();//执行Dispose后Timer就不会再申请新的线程了,可是仍是会给Timmer已经激发的事件申请线程
                sw.Stop();
            }

            if (c < 80)
                Console.WriteLine("Now:" + c.ToString());
            else
            {
                Console.WriteLine("Now:" + c.ToString()+"-----------Timer已经Dispose耗时:"+sw.ElapsedMilliseconds.ToString()+"毫秒");
            }

            if (flag)
            {
                Thread.Sleep(sleep);//模拟花时间的代码
            }
            else
            {
                if(i<=80)
                    Thread.Sleep(sleep);//前80次模拟花时间的代码
            }
        }

        public static void Init(int p_sleep,bool p_flag)
        {
            sleep = p_sleep;
            flag = p_flag;
            timer = new System.Threading.Timer(Excute, null, 0, 10);
        }
    }

    class SafeTimer
    {
        static int i = 0;
        static System.Threading.Timer timer;

        static bool flag = true;
        static object mylock = new object();

        static void Excute(object obj)
        {
            Thread.CurrentThread.IsBackground = false;

            lock (mylock)
            {
                if (!flag)
                {
                    return;
                }

                i++;

                if (i == 80)
                {
                    timer.Dispose();
                    flag = false;
                }
                Console.WriteLine("Now:" + i.ToString());
            }

            Thread.Sleep(1000);//模拟花时间的代码
        }

        public static void Init()
        {
            timer = new System.Threading.Timer(Excute, null, 0, 10);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("是否使用安全方法(Y/N)?");
            string key = Console.ReadLine();
            if (key.ToLower() == "y")
                SafeTimer.Init();
            else
            {
                Console.Write("请输入Timmer响应事件的等待时间(毫秒):");///这个时间直接决定了前80个任务的执行时间,由于等待时间越短,
///每一个任务就能够越快执行完,那么80个任务中就有越多的任务能够用到前面任务执行完后释放掉的线程,
///也就有越多的任务没必要去线程池申请新的线程避免多等待半秒钟的申请时间
                string sleep = Console.ReadLine();
                Console.Write("申请了80个线程后Timer剩余激发的线程请求是否须要等待时间(Y/N)?");///这里能够发现选Y或者N只要等待时间不变,
///最终Timer激发线程的次数都相近,说明Timer的确在执行80次的Dispose后就再也不激发新的线程了
                key = Console.ReadLine();
                bool flag = false;
                if (key.ToLower() == "y")
                {
                    flag = true;
                }

                UnSafeTimer.sw.Start();
                UnSafeTimer.Init(Convert.ToInt32(sleep), flag);
            }

            Console.ReadLine();
        }
    }
}

这个例子包含了两个Timer的类UnSafeTimer和SafeTimer,两个类的代码的大体意思就是使用Timer每隔10毫秒就执行Excute函数,Excute函数会显示当前执行的次数,在80次的时候经过timer.Dispose()让Timer中止再也不激发响应事件。

首先咱们来分析下UnSafeTimer
操作系统

class UnSafeTimer
{
    static int i = 0;
    static System.Threading.Timer timer;
    static object mylock = new object();
    static int sleep;
    static bool flag;
    public static Stopwatch sw = new Stopwatch();

    static void Excute(object obj)
    {
        Thread.CurrentThread.IsBackground = false;
        int c;

        lock (mylock)
        {
            i++;
            c = i;
        }

        if (c == 80)
        {
            timer.Dispose();//执行Dispose后Timer就不会再申请新的线程了,可是仍是会给Timmer已经激发的事件申请线程
            sw.Stop();
        }

        if (c < 80)
            Console.WriteLine("Now:" + c.ToString());
        else
        {
            Console.WriteLine("Now:" + c.ToString() + "-----------Timer已经Dispose耗时:" + sw.ElapsedMilliseconds.ToString() + "毫秒");
        }

        if (flag)
        {
            Thread.Sleep(sleep);//模拟花时间的代码
        }
        else
        {
            if (i <= 80)
                Thread.Sleep(sleep);//前80次模拟花时间的代码
        }
    }

    public static void Init(int p_sleep, bool p_flag)
    {
        sleep = p_sleep;
        flag = p_flag;
        timer = new System.Threading.Timer(Excute, null, 0, 10);
    }
}
你能够执行试一试,在输入是否执行安全方法的时候选N,等待时间1000,申请了80个线程后Timer剩余激发的线程选N,原本想在80次的时候停下来,但是你会发现直到执行到660屡次以后才停下来(具体看机器配置),申请前80个线程的时间为10532毫秒,反正执行的次数大大超出了限制的80次,回头想一想让Timer不在激发事件的方法是调用timer.Dispose(),难不成是Dispose有延迟?延迟的过程当中多执行了500屡次?那么咱们再来作个试验,咱们在申请了80个线程后Timer剩余激发的线程选y,请耐心等待结果,在最后你会发现执行时间仍是660次左右,这很显然是不合理的,若是Dispose有延迟时间形成所执行500屡次,那么加长80次后面每一个线程的申请时间在相同的延迟时间内申请的线程数应该减小,由于后面500多个线程每一个线程都要执行1000毫秒,那么势必有些线程会去申请新的线程有半秒钟的等待时间(你会发现申请了80个线程后Timer剩余激发的线程选y明显比选n慢得多,就是由于这个缘由),因此看来不是由于Dispose形成的。

那么会是什么呢?咱们此次这样选在输入是否执行安全方法的时候选N,等待时间500,申请了80个线程后Timer剩余激发的线程选N。
.net


那么会是什么呢?咱们此次这样选在输入是否执行安全方法的时候选N,等待时间50,申请了80个线程后Timer剩余激发的线程选N。
pwa


咱们发现随着每次任务等待时间的减小多执行的次数也在减小,最关键的一点咱们从图中能够看到,前80次任务申请的时间也在减小,这是最关键的,根据上面线程池所讲的内容咱们能够概括出:每次任务的等待时间直接决定了前80个任务的执行时间,由于等待时间越短,每一个任务就能够越快执行完,那么80个任务中就有越多的任务能够用到前面任务执行完后释放掉的线程,也就有越多的任务没必要去线程池申请新的线程避免多等待半秒钟的申请时间,而Timer并不会去关心线程池申请前80个任务的时间长短,只要它没有执行到timer.Dispose(),它就会每隔10毫秒激发一次响应时间,无论前80次任务执行时间是长仍是短,timer都在第80次任务才执行Dispose,执行Dispose后timer就不会激发新的事件了,可是若是前80次任务申请的时间越长,那么timer就会在前80次任务申请的时间内激发越多响应事件,那么线程池中等待队列中就会有越多的响应函数等待申请线程,System.Threading.Timer没有机制取消线程池等待队列中多余的申请数,因此致使等待时间越长,80次后执行的任务数越多。
线程

由此只用timer.Dispose()来终止Timer激发事件是不安全的,因此又写了个安全的执行机制:

class SafeTimer
{
    static int i = 0;
    static System.Threading.Timer timer;

    static bool flag = true;
    static object mylock = new object();

    static void Excute(object obj)
    {
        Thread.CurrentThread.IsBackground = false;

        lock (mylock)
        {
            if (!flag)
            {
                return;
            }

            i++;

            if (i == 80)
            {
                timer.Dispose();
                flag = false;
            }
            Console.WriteLine("Now:" + i.ToString());
        }

        Thread.Sleep(1000);//模拟花时间的代码
    }

    public static void Init()
    {
        timer = new System.Threading.Timer(Excute, null, 0, 10);
    }
}
安全类中咱们用了个bool类型的变量flag来判断当前是否执行到80次了,执行到80次后将flag置为false,而后timer.Dispose,这时虽然任务仍是要多执行不少次可是因为flag为false,Excute函数一开始就作了判断flag为false会当即退出,Excute函数80次后至关于就不执行了。

System.Timers.Timer

在上面的例子中咱们看到System.Threading.Timer很不安全,即便在安全的方法类,也只能让事件响应函数在80次后马上退出让其执行时间近似于0,可是仍是浪费了系统很多的资源。

因此本人更推荐使用如今介绍的System.Timers.Timer,System.Timers.Timer大体原理和System.Threading.Timer差很少,惟一几处不一样的就是:

  • 构造函数不一样,构造函数能够什么事情也不作,也能够传入响应间隔时间:System.Timers.Timer timer = new System.Timers.Timer(10);
  • 响应事件的响应函数不在构造函数中设置:timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
  • 声明System.Timers.Timer对象后他不会自动执行,须要调用 timer.Start()或者timer.Enabled = true来启动它, timer.Start()的内部原理仍是设置timer.Enabled = true
  • 调用 timer.Stop()或者timer.Enabled = false来中止引起Elapsed事件, timer.Stop()的内部原理仍是设置timer.Enabled = false,最重要的是timer.Enabled = false后会取消线程池中当前等待队列中剩余任务的执行。

那么咱们来看个例子:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Threading;

namespace ConsoleApplication2
{
    class UnSafeTimer
    {
        static int i = 0;
        static System.Timers.Timer timer;
        static object mylock = new object();

        public static void Init()
        {
            timer = new System.Timers.Timer(10);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }

        static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Thread.CurrentThread.IsBackground = false;
            int c;

            lock (mylock)
            {
                i++;
                c = i;
            }

            Console.WriteLine("Now:" + i.ToString());

            if (c == 80)
            {
                timer.Stop();//可应看到System.Timers.Timer的叫停机制比System.Threading.Timer好得多,
///就算在不安全的代码下Timer也最多多执行一两次(我在试验中发现有时会执行到81或82),
///说明Stop方法在设置Timer的Enable为false后不只让Timer再也不激发响应事件,还取消了线程池等待队列中等待得到线程的任务,
///至于那多执行的一两次任务我我的认为是Stop执行过程当中会耗费一段时间才将Timer的Enable设置为false,这段时间多余的一两个任务就得到了线程开始执行
            }


            Thread.Sleep(1000);//等待1000毫秒模拟花时间的代码,注意:这里的等待时间直接决定了80(因为是不安全模式有时会是81或8二、83)个任务的执行时间,
///由于等待时间越短,每一个任务就能够越快执行完,那么80个任务中就有越多的任务能够用到前面任务执行完后释放掉的线程,
///也就有越多的任务没必要去线程池申请新的线程避免多等待半秒钟的申请时间
        }
    }

    class SafeTimer
    {
        static int i = 0;
        static System.Timers.Timer timer;

        static bool flag = true;
        static object mylock = new object();

        public static void Init()
        {
            timer = new System.Timers.Timer(10);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start(); 
        }

        static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Thread.CurrentThread.IsBackground = false;

            lock (mylock)
            {
                if (!flag)
                {
                    return;
                }
                i++;

                Console.WriteLine("Now:" + i.ToString());

                if (i == 80)
                {
                    timer.Stop();
                    flag = false;
                }
            }

            Thread.Sleep(1000);//同UnSafeTimer
        }

        class Program
        {
            static void Main(string[] args)
            {
                Console.Write("是否使用安全Timer>(Y/N)?");
                string Key = Console.ReadLine();

                if (Key.ToLower() == "y")
                    SafeTimer.Init();
                else
                    UnSafeTimer.Init();

                Console.ReadLine();
            }
        }
    }
}
这个例子和System.Threading.Timer差很少,这里也分为:安全类SafeTimer和不安全类UnSafeTimer,缘由是 timer.Stop()有少量的延迟时间有时任务会执行到81~83,可是就算是不安全方法也就最多多执行几回,不像System.Threading.Timer多执行上百次...

因此我这里仍是推荐你们使用System.Timers.Timer。

相关文章
相关标签/搜索