CODE-SetTimeout/ClearTimeout in C#
3 |
正在從事以休閒為目的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
}
}
重點補充:
- SetTimeout的原理是呼叫端傳入Action參數及延遲微秒(ms)數後,立即新增一條Thread執行"先Thread.Delay()指定時間長度再呼叫所傳入的Action",並將控制權交回呼叫端,邏輯十分單純。
- 由於SetTimeout傳入要延遲執行的Action程式實際上會由另一條Thread執行,若在該邏輯中變動Window Form上的元素,就會違背不可透過非UI Thread去更動UI元素的規則。因此,我們再多增加一個Form參數,當有需要時,透過Form.Invoke()間接執行才可避開UI Thread限制。
- 要能ClearTimeout,就必須保留Thread變數,必要時將其Abort()掉。
我用了一個Diction<Guid, Thread>來保存Thread,SetTimeout時Dictionary.Add()並傳回一個GUID,ClearTimeout時可憑該GUID去中止尚未執行的排程。 - 理論上,ClearTimeout只能中止"仍在等待執行的工作",因此Thread.Delay一結束時,就立刻Dictionary.Remove(GUID) (這裡剛好展現了Closure的美妙之處),不再允許ClearTimeout,否則程式都跑了一半還胡亂Thread.Abort()會出人命的。
寫完再回頭看,短短30行Code還真扯到不少進階的東西(Action, Closure, Threading),看懂的人C# Coding能力應該都在中階以上了吧!(純個人意見,勿戰) 對這一段還不熟的人可以參考以前PO過的幾篇文章當起頭,但我想還需要參考官方文件、網路範例配合實地動手寫過才容易完全理解。
【延伸閱讀】
Comments
# by chicken
提供一個簡單一點的作法... .NET FX 裡光是 timer 就有三種之多 =_=,其中掛在 System.Threading 這個 namespace 裡的 Timer, 作用就是要解決這篇的問題... timer 時間到了,會直接把工作丟到 thread pool 裡去執行,用這個 timer 正好可以省掉自己控制 thread 的細節..
# by Billy
似乎用System.Timers.Timer 是最好的方法了。 讓我補充兩種.net timer 的reference: System.Timers.Timer http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx System.Threading.Timer http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx Their comparison: http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2
# by Jeffrey
<p>to chicken, 一開始有考慮過System.Threading.Timer, 不過卡在一個迷思(心魔? 呵),即便把period設成Timeout.Infinite,但Timer生命週期仍會持續,必須要手動銷毁物件才真的會被回收,若要加上額外銷毁機制就未必划算。(但我不確定這個推想是否為真,得實際玩看看才知) </p> <p>另外,ClearTimeout時是否能避開執行中的Callback也待研究(但背後若是用ThreadPool的話應該不會有問題),不像自己控管Thread這麼一目了然。</p> <p>to Billy, System.Timers.Timer可以自動解決UI Thread限制是一大賣點,不過不像Threading.Timer有dueTime可以簡單做出延遲執行效果。而生命週期及ClearTimeout的問題也跟Threading.Timer同。</p> <p>看來幾個Solution各有優劣,以乎沒有一面倒的結論。</p>