C# 多线程中的lock与token模式

 

先看示例:

我们创建一个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();
            }
        }
    }

测试后发现,确实没有出现之前的问题。

 

 

posted @ 2014-06-08 23:58  Hi-Jimmy  阅读(60)  评论(0编辑  收藏  举报