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

測試後發現,確實沒有出現之前的問題。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章