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。

相關文章
相關標籤/搜索