C#的三種定時器


三種定時器:

·關於C#中timer類 在C#里關於定時器類就有3個

 

1、基於 Windows 的標准計時器(System.Windows.Forms.Timer)

2、基於服務器的計時器(System.Timers.Timer)

3、線程計時器(System.Threading.Timer)

 

System.Windows.Forms.Timer是應用於WinForm中的,它是通過Windows消息機制實現的,類似於VB或Delphi中 的Timer控件,內部使用API SetTimer實現的。它的主要缺點是計時不精確,而且必須有消息循環,Console Application(控制台應用程序)無法使用。

 System.Timers.Timer和System.Threading.Timer非常類似,它們是通過.NET Thread Pool實現的,輕量,計時精確,對應用程序、消息沒有特別的要求。System.Timers.Timer還可以應用於WinForm,完全取代上面的 Timer控件。它們的缺點是不支持直接的拖放,需要手工編碼。

 

例:

使用System.Timers.Timer類

System.Timers.Timer t = new System.Timers.Timer(10000);//實例化Timer類,設置間隔時間為10000毫秒;

t.Elapsed += new System.Timers.ElapsedEventHandler(theout);//到達時間的時候執行事件;

t.AutoReset = true;//設置是執行一次(false)還是一直執行(true);

t.Enabled = true;//是否執行System.Timers.Timer.Elapsed事件;

 

public void theout(object source, System.Timers.ElapsedEventArgs e)

{

MessageBox.Show("OK!");

}

 

 

一、基於 Windows 的標准計時器(System.Windows.Forms.Timer)

微軟的注釋很明確:“實現按用戶定義的時間間隔引發事件的計時器。此計時器最宜用於 Windows 窗體應用程序中,並且必須在窗口中使用。”,在線程中使用,其相應的事件是不會觸發的。

首先注意一點就是:Windows 計時器是為單線程環境設計的,此計時器從Visual Basic 1.0 版起就存在於該產品中,並且基本上未做改動,這個計時器是使用最簡單的一種,只要把工具箱中的Timer控件拖到窗體上,然后設置一下事件和間隔時間等屬性就可以了,實驗出來的結果也完全符合單線程的特點

   1、當啟動此計時器后,會在下方子線程ID列表中顯示子線程ID,並且和主線程ID相同

   private void formsTimer_Tick(object sender, EventArgs e)
   {
     i++;
     lblSubThread.Text += "子線程執行,線程ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() + "\r\n";
   }

  2、當單擊主線程暫停5秒后,子線程會暫停執行,並且當5秒之后不會執行之前被暫停的子線程,而是直接執行后面的子線程(也就是會少輸出幾行值)

   System.Threading.Thread.Sleep(5000);

    3、在子進程的事件中暫停5秒會導致主窗口相應無響應5秒

       4、定義一個線程靜態變量

   [ThreadStatic]

   private static int i = 0;

   在子線程事件中每次加一,再點擊線程靜態變量值會得到增加后的i值

 

二、基於服務器的計時器(System.Timers.Timer)

   System.Timers.Timer不依賴窗體,是從線程池喚醒線程,是傳統的計時器為了在服務器環境上運行而優化后的更新版本,在VS2005的工具箱中沒有提供現成的控件,需要手工編碼使用此計時器。

   使用方式有兩種,

   1、通過SynchronizingObject屬性依附於窗體

   System.Timers.Timer  timersTimer = new System.Timers.Timer();

   timersTimer.Enabled = false;

   timersTimer.Interval = 100;

   timersTimer.Elapsed += new System.Timers.ElapsedEventHandler(timersTimer_Elapsed);

   timersTimer.SynchronizingObject = this;

   通過這種方式來使用,實驗效果幾乎和基於 Windows 的標准計時器一樣,只是在上面的第二條實驗中,雖然也會暫停子線程的執行,不過在5秒之后把之前排隊的任務都執行掉(也就是不會少輸出幾行值)

 

  2、不使用SynchronizingObject屬性

   這種方式就是多線程的方式了,即啟動的子線程和主窗體不在一個線程。不過這樣也存在一個問題:由於子線程是單獨的一個線程,那么就不能訪問住窗體中的控件了,只能通過代理的方式來訪問:

   delegate void SetTextCallback(string text);

void timersTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
  //使用代理
  string text = "子線程執行,線程ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() + "\r\n";
  SetTextCallback d = new SetTextCallback(SetText);
  this.Invoke(d, new object[] { text });
  i++;
}

private void SetText(string text)
{
  lblSubThread.Text += text;
}

 

  這樣我們再次實驗就會得到如下的結果:

   1、當啟動此計時器后,會在下方子線程ID列表中顯示子線程ID,並且和主線程ID不相同

   2、當單擊主線程暫停5秒后,子線程會一直往下執行(界面上可能看不出來,不過通過在子線程輸出文件的方式可以很方便的看出來)

   3、在子進程的事件中暫停5秒不會導致主窗口無響應

   4、在子線程事件中每次給線程靜態變量加一,再點擊線程靜態變量值得到的值還是0(不會改變主窗口中的線程靜態變量

 

三、線程計時器(System.Threading.Timer)

   線程計時器也不依賴窗體,是一種簡單的、輕量級計時器,它使用回調方法而不是使用事件,並由線程池線程提供支持。

   對消息不在線程上發送的方案中,線程計時器是非常有用的。

   使用方法如下:

System.Threading.Timer threadTimer;
public void ThreadMethod(Object state)
{
  //使用代理
  string text = "子線程執行,線程ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() + "\r\n";
  SetTextCallback d = new SetTextCallback(SetText);
  this.Invoke(d, new object[] { text });
  i++;
}

private void Form1_Load(object sender, EventArgs e)
{
  threadTimer = new System.Threading.Timer(new System.Threading.TimerCallback(ThreadMethod), null, -1, -1);
}

 

System.Threading.Timer 是由線程池調用的。所有的Timer對象只使用了一個線程來管理。這個線程知道下一個Timer對象在什么時候到期。下一個Timer對象到期時,線程就會喚醒,在內部調用ThreadPool 的 QueueUserWorkItem,將一個工作項添加到線程池隊列中,使你的回調方法得到調用。如果回調方法的執行時間很長,計時器可能(在上個回調還沒有完成的時候)再次觸發。這可能造成多個線程池線程同時執行你的回調方法。

周期到就會再次調用回調方法,而不管上次是否執行完成,形成多個線程池線程同時執行!

參數
callback : 一個Object 類型參數的委托,周期調用的函數。
state: callback 委托調用時的參數。
dueTime: 定時器延時多久開始調用。單位 毫秒
period: 定時器每隔多久調用一次。單位 毫秒

不能使用局部變量來創建指向一個線程定時器。因為局部變量會被GC回收,導致定時器失效。
比如下面的例子:

static void Main(string[] args) 
{
    Run();
    //為了加開GC垃圾回收,不停的創建對象
    for (int i = 0; i < 10000; i++) 
    {
        cc tmp = new cc();
        Thread.Sleep(10);
    }
    Console.ReadKey();
}

static int id;

static void Run() 
{
    Timer timer = new Timer(DoTime, null, 500, 500);
}

static void DoTime(object obj) 
{
    Console.WriteLine(id++);
}

class cc
{ 
    public int[] a = new int[1000];
}

 

定時器執行4次后停止了。定時器什么時候停止取決於GC什么時候回收。如果一直沒有GC的回收,那么將會一直執行下去。比如把上方創建 cc 對象的代碼刪除。定時器將會一直執行。

可以在Main方法中使局部變量 或者 使用全局變量來存放Timer 對象 避免 clr 把Timer 回收。

        static Timer timer;

        static void Main(string[] args) {

            timer =  new Timer(DoTime, null, 500, 500);

            //為了加開GC垃圾回收,不停的創建對象
            for (int i = 0; i < 10000; i++) {
                cc tmp = new cc();
                Thread.Sleep(10);
            }

            Console.ReadKey();

        }

        static int id;

        static void DoTime(object obj) {
            Console.WriteLine(id++);
        }

如果回調方法的執行時間很長,計時器可能(在上個回調還沒有完成的時候)再次觸發。這可能造成多個線程池線程同時執行你的回調方法。

 

        static Timer timer1;

        static void Main(string[] args) {

            timer1 = new Timer(DoTime, 1, 500, 500);
            Console.ReadKey();
        }

        static int id;

        static void DoTime(object obj) {
            int idx = id ++;
            Console.WriteLine("處理開始:" + idx + "," + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1200);
            Console.WriteLine("處理完畢:" + idx + "," + Thread.CurrentThread.ManagedThreadId);
        }

定時器第一次任務還沒執行完畢,第二次,第三次回調就開始了。多個線程同時並發 DoTime 方法。

回調方法運行時,id的值被COPY到線程空間,不會被其它線程的操作改變,每個DoTime 方法都有自己的線程空間。

 

 

解決方法【取消周期調用
period 【周期調用時間】使用 Timeout.Infinite。這個參數將導致DoTime 只處理一次。
在回調方法中使用 Change方法來修改 dueTime【延時時間】,period 【周期時間】參數。period 繼續使用 Timeout.Infinite. 使用這個方法要注意如果timer 在被Dispose了,使用Change 將會引發異常
比如

        static Timer timer1;

        static void Main(string[] args) {
            timer1 = new Timer(DoTime, 1, 0, Timeout.Infinite);
            Console.ReadKey();
        }

        static int id;
        static void DoTime(object obj) {
            int idx = id ++;
            Console.WriteLine("處理開始:" + idx + "," + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1200);
            Console.WriteLine("處理完畢:" + idx + "," + Thread.CurrentThread.ManagedThreadId);
            timer1.Change(500,Timeout.Infinite);     //設置下次調用時間
        }

 

 

 

 

使用Disponse停止定時器
如果Timer 不會再使用 則可以 使用 Dispose 方法來停止定時器。
如果定時器運行到中途,使用Dispose方法后,callback還是會執行完一個完整的生命周期,不會中途停止。並且Dispose方法不會等待 callback的這次調用完成。只是定時器下次不再調用 callback。

使用Change停止定時器
把 dueTime 參數置為-1就可以停止定時器。同樣的,它不會中斷在運行中的callback,只是下一次不再回調。 這個方法停止的定時器 還可以使用Change 再次利用定時器

 四、c#每隔一段時間執行代碼

 

方法一:調用線程執行方法,在方法中實現死循環,每個循環Sleep設定時間;

方法二:使用System.Timers.Timer類;

方法三:使用System.Threading.Timer;

using System;
using System.Collections;
using System.Threading;
  
public class Test
{
  
    public static void Main()
    {
        Test obj = new Test();
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
  
        //方法一:調用線程執行方法,在方法中實現死循環,每個循環Sleep設定時間
        Thread thread = new Thread(new ThreadStart(obj.Method1));
        thread.Start();
  
  
        //方法二:使用System.Timers.Timer類
        System.Timers.Timer t = new System.Timers.Timer(100);//實例化Timer類,設置時間間隔
        t.Elapsed += new System.Timers.ElapsedEventHandler(obj.Method2);//到達時間的時候執行事件
        t.AutoReset = true;//設置是執行一次(false)還是一直執行(true)
        t.Enabled = true;//是否執行System.Timers.Timer.Elapsed事件
        while (true)
        {
            Console.WriteLine("test_" +Thread.CurrentThread.ManagedThreadId.ToString());
            Thread.Sleep(100);
        }
  
  
        //方法三:使用System.Threading.Timer
        //Timer構造函數參數說明:
        //Callback:一個 TimerCallback 委托,表示要執行的方法。
        //State:一個包含回調方法要使用的信息的對象,或者為空引用(Visual Basic 中為 Nothing)。
        //dueTime:調用 callback 之前延遲的時間量(以毫秒為單位)。指定 Timeout.Infinite 以防止計時器開始計時。指定零 (0) 以立即啟動計時器。
        //Period:調用 callback 的時間間隔(以毫秒為單位)。指定 Timeout.Infinite 可以禁用定期終止。
        System.Threading.Timer threadTimer = new System.Threading.Timer(new System.Threading.TimerCallback(obj.Method3),null, 0, 100);
        while (true)
        {
            Console.WriteLine("test_" +Thread.CurrentThread.ManagedThreadId.ToString());
            Thread.Sleep(100);
        } 
        Console.ReadLine();
    }
  
  
    void Method1()
    {
        while (true)
        {
            Console.WriteLine(DateTime.Now.ToString()+ "_" + Thread.CurrentThread.ManagedThreadId.ToString());
            Thread.CurrentThread.Join(100);//阻止設定時間
        }
    }
  
  
    void Method2(object source,System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(DateTime.Now.ToString()+ "_" + Thread.CurrentThread.ManagedThreadId.ToString());
    }
  
  
    void Method3(Objectstate)
    {
        Console.WriteLine(DateTime.Now.ToString()+ "_" +Thread.CurrentThread.ManagedThreadId.ToString());
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM