先看示例:
我們創建一個winform窗體,放入兩個button控件,以及一個ListBox控件。之後界面如圖所示:
功能:點擊start按鈕,從1開始不斷遞增(不斷+1),並將值顯示在右側Listbox內。
點擊stop停止遞增並回到起點1,同時清空Listbox。
要實現並不難。用一個線程控制數字的遞增,再將值賦值到UI中的Listbox。
初步實現如下:
public partial class Form1 : Form
{
private delegate void FileDetectedUI(int f);
System.Threading.Thread t;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)//點擊start按鈕
{
this.Stop();
t = new System.Threading.Thread(this.Detect);
t.IsBackground = true;
object[] args = new object[] { 0 };
t.Start(args);
}
private void Detect(object p)
{
object[] args = p as object[];
this.Detect((int)args[0]);
}
private void Detect(int value)
{
if (value > 99999) System.Threading.Thread.CurrentThread.Abort();
this.FileDetected(++value);
Detect(value);
}
private void FileDetected(int f)
{
FileDetectedUI action = this.DoFileDetected;
this.BeginInvoke(action, f);
System.Threading.Thread.Sleep(200);
}
private void DoFileDetected(int f)
{
listBox1.Items.Add(f);
}
private void button2_Click(object sender, EventArgs e)//點擊stop按鈕
{
this.Stop();
}
private void Stop()
{
if (t != null) t.Abort();
listBox1.Items.Clear();
}
}
結果如圖:
雖然功能似乎能實現,但是細心的你可能立馬發現了一個問題:
當我們快速點擊start按鈕時,數字出現了偶發錯亂,如圖:
出現這個問題是因爲,當我們點擊start按鈕重新開始線程時,某一瞬間,舊線程的數據通過委託加載到UI。雖然這時候我們負責數據遞增的線程已經重新開始(t線程被重新new),但是UI線程數據因爲一些延遲的原因將舊線程最後一次的數據記載到了Listbox中。
如圖,左側是負責數據遞增的線程,右側是UI線程。兩者之間有一定的延遲誤差。
當第二次按鈕Start按鈕時,UI部分才正在執行Item.Add(3)。雖然已經開始了新的線程,但是UI線程似乎並不買賬。
對於這個問題。我們可以啓用某種標識來進行約束。如GUID。
思路:全局有一個GUID,在爲UI線程傳參的時候可以將其傳進去,當UI部分進行Item.Add()的時候,必須判斷參數GUID是否與當前全局GUID相等,相等則執行Item.Add(),每次的線程重啓時,修改全局GUID。
這樣,當第二次按下start按鈕時,全局GUID已經修改,註定與以往傳參的GUID不同,所以不會執行Item.Add().
按照這個思路,我們結合lock,修改Form代碼如下:
public partial class Form1 : Form
{
private Guid token;
private object locker = new object();
private delegate void FileDetectedUI(int f, Guid token);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
lock (locker)
{
this.Stop();
token = Guid.NewGuid();
System.Threading.Thread t = new System.Threading.Thread(this.Detect);
t.IsBackground = true;
object[] args = new object[] { 0, token };
t.Start(args);
}
}
private void Detect(object p)
{
object[] args = p as object[];
this.Detect((int)args[0], (Guid)args[1]);
}
private void Detect(int value, Guid token)
{
if (!token.Equals(this.token) || value > 99999) System.Threading.Thread.CurrentThread.Abort();
this.FileDetected(++value, token);
Detect(value, token);
}
private void FileDetected(int f, Guid token)
{
lock (locker)
{
if (token.Equals(this.token))
{
FileDetectedUI action = this.DoFileDetected;
this.BeginInvoke(action, f, token);
}
}
System.Threading.Thread.Sleep(200);
}
private void DoFileDetected(int f, Guid token)
{
lock (locker)
{
if (token.Equals(this.token))
{
listBox1.Items.Add(f);
}
}
}
private void button2_Click(object sender, EventArgs e)
{
this.Stop();
}
private void Stop()
{
lock (locker)
{
token = Guid.NewGuid();
listBox1.Items.Clear();
}
}
}
測試後發現,確實沒有出現之前的問題。