• 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();
                }
            }
        }
    

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

    作者:Mr.Jimmy
    出处:https://www.cnblogs.com/JHelius
    联系:yanyangzhihuo@foxmail.com
    如有疑问欢迎讨论,转载请注明出处

  • 相关阅读:
    数据库插入数据返回当前主键ID值方法
    兼容SQLSERVER、Oracle、MYSQL、SQLITE的超级DBHelper
    C# listview 单击列头实现排序 <二>
    C# ListView点击列头进行排序
    MessageBox.Show()的各种用法
    QT 删除文件指定目录
    hihoCoder 1015 KMP算法
    hiho一下 第五十周 (求欧拉路径)
    hdu
    hiho一下 第四十九周 欧拉路
  • 原文地址:https://www.cnblogs.com/JHelius/p/14318911.html
Copyright © 2020-2023  润新知