CODE-SetTimeout/ClearTimeout in C#

正在從事以休閒為目的Coding活動時,忽然有個衝動想在C#中也用一下Javascript裡常用的setTimeout/clearTimeout。

setTimeout說穿了就是透過另一條Thread執行程式產生非同步效果,用.NET實作是小菜一碟,而我想挑戰的是如何用最簡潔的方法實作出來。

剛好這陣子陸續玩過Action<T> and Func<T>Closure in C#,加上研究Parallel.For()時被迫反覆寫了十來次,現在已經練就信手就可掰出一段Thread配Lambda範例的境界。這個題目拿來作為隨堂考試再適合也不過了。

在Action加Lambda加Closure的加持之下,只要不到30行程式就可以在C#中使用SetTimeout/ClearTimeout囉~~~ C#真是簡潔有力的好語言呀!

先看程式碼,後面再提一下重點:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Threading;
 
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            //No UI thread issue, use SetTimeout(cb, delay)
            SetTimeout(() =>
            {
                MessageBox.Show("First");
            }, 2000);
            SetTimeout(() =>
            {
                listBox1.Items.Add("Second");
            }, 4000, this);
            Guid hnd = SetTimeout(() =>
            {
                listBox1.Items.Add("Third");
            }, 6000, this);
            SetTimeout(() =>
            {
                listBox1.Items.Add("Forth");
            }, 8000, this);
            ClearTimeout(hnd);
        }
 
        #region SetTimeout/ClearTimeout Simulation
        //Dictionary for running setTimeout
        static Dictionary<Guid, Thread> _setTimeoutHandles =
            new Dictionary<Guid, Thread>();
        //SetTimeout for no UI Thread issue
        static Guid SetTimeout(Action cb, int delay)
        {
            return SetTimeout(cb, delay, null);
        }
        //Javascript-style SetTimeout function
        //remember to set uiForm argument when there cb is trying
        //to change UI controls in window form
        //it will return a GUID as handle for cancelling
        static Guid SetTimeout(Action cb, int delay, Form uiForm)
        {
            Guid g = Guid.NewGuid();
            Thread t = new Thread(() =>
            {
                Thread.Sleep(delay);
                _setTimeoutHandles.Remove(g);
                if (uiForm != null)
                    //use Invoke() to avoid threading issue
                    //Ref: http://tinyurl.com/yjckzhz
                    uiForm.Invoke(cb);
                else
                    cb();
            });
            _setTimeoutHandles.Add(g, t);
            t.Start();
            return g;
        }
        //Javascript-style ClearTimeout
        static void ClearTimeout(Guid g)
        {
            if (!_setTimeoutHandles.ContainsKey(g))
                return;
            _setTimeoutHandles[g].Abort();
            _setTimeoutHandles.Remove(g);
        }
        #endregion
    }
}

重點補充:

  1. SetTimeout的原理是呼叫端傳入Action參數及延遲微秒(ms)數後,立即新增一條Thread執行"先Thread.Delay()指定時間長度再呼叫所傳入的Action",並將控制權交回呼叫端,邏輯十分單純。
  2. 由於SetTimeout傳入要延遲執行的Action程式實際上會由另一條Thread執行,若在該邏輯中變動Window Form上的元素,就會違背不可透過非UI Thread去更動UI元素的規則。因此,我們再多增加一個Form參數,當有需要時,透過Form.Invoke()間接執行才可避開UI Thread限制。
  3. 要能ClearTimeout,就必須保留Thread變數,必要時將其Abort()掉。
    我用了一個Diction<Guid, Thread>來保存Thread,SetTimeout時Dictionary.Add()並傳回一個GUID,ClearTimeout時可憑該GUID去中止尚未執行的排程。
  4. 理論上,ClearTimeout只能中止"仍在等待執行的工作",因此Thread.Delay一結束時,就立刻Dictionary.Remove(GUID) (這裡剛好展現了Closure的美妙之處),不再允許ClearTimeout,否則程式都跑了一半還胡亂Thread.Abort()會出人命的。

寫完再回頭看,短短30行Code還真扯到不少進階的東西(Action, Closure, Threading),看懂的人C# Coding能力應該都在中階以上了吧!(純個人意見,勿戰) 對這一段還不熟的人可以參考以前PO過的幾篇文章當起頭,但我想還需要參考官方文件、網路範例配合實地動手寫過才容易完全理解。

【延伸閱讀】

  1. Lambda演算式
  2. 如何透過Lambda精簡Threading程式
  3. Action<T> and Func<T>
  4. UI Thread限制
  5. Closure in C#
Published 07 February 2010 11:44 AM 由 Jeffrey
Filed under:


意見

# chicken said on 07 February, 2010 11:36 PM

提供一個簡單一點的作法...  .NET FX 裡光是 timer 就有三種之多 =_=,其中掛在 System.Threading 這個 namespace 裡的 Timer, 作用就是要解決這篇的問題...

timer 時間到了,會直接把工作丟到 thread pool 裡去執行,用這個 timer 正好可以省掉自己控制 thread 的細節..

# Billy said on 08 February, 2010 08:17 AM

似乎用System.Timers.Timer 是最好的方法了。

讓我補充兩種.net timer 的reference:

System.Timers.Timer

msdn.microsoft.com/.../system.timers.timer.aspx

System.Threading.Timer

msdn.microsoft.com/.../system.threading.timer.aspx

Their comparison:

msdn.microsoft.com/.../cc164015.aspx

# Jeffrey said on 08 February, 2010 09:03 AM

to chicken, 一開始有考慮過System.Threading.Timer, 不過卡在一個迷思(心魔? 呵),即便把period設成Timeout.Infinite,但Timer生命週期仍會持續,必須要手動銷毁物件才真的會被回收,若要加上額外銷毁機制就未必划算。(但我不確定這個推想是否為真,得實際玩看看才知)

另外,ClearTimeout時是否能避開執行中的Callback也待研究(但背後若是用ThreadPool的話應該不會有問題),不像自己控管Thread這麼一目了然。

to Billy, System.Timers.Timer可以自動解決UI Thread限制是一大賣點,不過不像Threading.Timer有dueTime可以簡單做出延遲執行效果。而生命週期及ClearTimeout的問題也跟Threading.Timer同。

看來幾個Solution各有優劣,以乎沒有一面倒的結論。

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 

請輸入以上的數字:

搜尋

Go

<February 2010>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
28123456
78910111213
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication