先看示例:
我们创建一个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();
}
}
}
测试后发现,确实没有出现之前的问题。