2022/06/13

C# Thread

Thread 相關類別

有三個相關的類別:Thread, ThreadStart, ParameterizedThreadStart

  • ThreadStart

    宣告的 function會被 thread 執行,ThreadStart 負責建立無參數的委派函式

  • ParameterizedThreadStart

    宣告的 function會被 thread 執行,ThreadStart 負責建立有參數的委派函式

  • Thread

    建立、控制、管理 Thread,常用的屬性:

    屬性 說明 回傳資料型別
    CurrentThread 目前正在執行的 thread Thread
    IsAlive thread 的執行狀態 Boolean
    IsBackground thread 是否為背景執行緒 Boolean
    ManagedThreadId thread 的識別號碼 Integer
    Name thread 的名稱 String 或 null
    ThreadState thread 的狀態 ThreadState (enum)

    常用 method

    method 說明
    Abort() 停止 thread,停止後,無法重新啟動
    BeginCriticalRegion() 設定 Critical region
    EndCriticalRegion() 結束 Critical region
    Interrupt() 中斷 WaitSleepJoin 狀態的 thread
    Join([a]) 封鎖執行緒直到停止執行為止,a 為 integer (ms) 或 TimeSpan
    ResetAbort() 取消正在要求的 Abort()
    Sleep(a) 暫停執行緒 a (ms) 或 TimeSpan
    Start([a]) 啟動 thread,a為委派函式的參數,object 型別

    Note: 使用 Abort() 不保證一定能停止 thread,有可能會發生 exception。ex: SecurityException: 沒有權限停止 thread,ThreadStateException: 停止已暫停的 thread

  • ThreadState (enum)

    位於 System.Threading namespace

    列舉常數 value 說明
    Aborted 256 執行緒目前無作用,但狀態尚未變更為 Stopped
    AbortRequested 128 已收到 Abort()
    Background 4 背景執行
    Running 0 正在執行
    Stopped 16 已停止
    StopRequested 1 已被要求停止中
    Suspended 64 已暫停
    SuspendedRequested 2 已被要求暫停中
    Unstarted 8 還沒開始執行,未被呼叫 Start()
    WaitSleepJoin 32 已被封鎖

    列出呼叫哪個 method 會導致狀態改變

    method state
    建立 thread Unstarted
    Start() Running
    Sleep() WaitSleepJoin
    對另一個物件呼叫 Monitor.Wait() WaitSleepJoin
    Join WaitSleepJoin
    Interrupt() Runing
    Suspend() SuspendedRequested
    回應 Suspend() Suspended
    Resume() Running
    Abort() AbortRequested
    回應 Abort() Stopped
    thread 已終止 Stopped

建立 thread

  1. 建立委派的 method

  2. 使用 ThreadStart 建立委派物件

  3. 使用委派物件建立 Thread 型別的 Thread 物件

  4. Start()

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        bool fgDone;
        Int32 sum;
        int guess;

        public Form1()
        {
            InitializeComponent();
        }

        void count()
        {            
            while (sum < int.MaxValue)
                sum++;

            fgDone = true;
        }

        void count_param(object num)
        {
            Random rd = new Random();

            while(guess!=(int)num)
            {
                guess = rd.Next(1, 101);
                Thread.Sleep(100);
            }                     

            fgDone = true;
        }

        // 沒有用 thread,計算過程中,視窗會卡住,無法使用
        private void Button1_Click(object sender, EventArgs e)
        {          
            textBox1.AppendText("開始計算\r\n");
            sum = 0;

            while (sum < int.MaxValue)
                sum++;

            textBox1.AppendText("計算完畢,sum= " + 
                sum.ToString()+"\r\n");
        }

        // 用 ThreadStart 產生 Thread
        private void button2_Click(object sender, EventArgs e)
        {
            ThreadStart thdStart = new ThreadStart(count);
            Thread thd = new Thread(thdStart);

            sum = 0;
            fgDone = false;
            textBox1.AppendText("執行緒開始執行\r\n");

            thd.Start();
            timer1.Enabled = true;
        }

        // 用 ParameterizedThreadStart 產生 Thread
        private void button3_Click(object sender, EventArgs e)
        {
            int num = 78;
            ParameterizedThreadStart paramStart = 
                new ParameterizedThreadStart(count_param);
            Thread thd = new Thread(paramStart);

            fgDone = false;
            guess = -1;
            thd.Start(num);
            timer2.Enabled = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (fgDone)
            {
                timer1.Enabled = false;
                textBox1.AppendText("計算完畢,sum= " +
                    sum.ToString()+"\r\n");
            }
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            textBox1.AppendText(guess.ToString() + "\r\n");
            if (fgDone)
            {
                timer2.Enabled = false;
                textBox1.AppendText("找到了\r\n");
            }
        }
    }
}

取得 thread 執行結果

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Int32 sum;

        public Form1()
        {
            InitializeComponent();
        }

        void count()
        {
            while (sum < int.MaxValue)
                sum++;           
        }

        void count_param(object param)
        {
            Int32[] pp = (Int32[])param;

            while (pp[0] < int.MaxValue)
                pp[0]++;           
        }

        // 透過 sum 全域變數,儲存 thread 的執行結果
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count);

            sum = 0;
            textBox1.AppendText("執行緒開始執行\r\n");

            thd.Start();
            textBox1.AppendText("使用Join()方法,"+"" +
                "所以必須等待執行緒執行結束...\r\n");
            thd.Join();
            textBox1.AppendText("sum=" + sum.ToString() + 
                "\r\n");            
        }

        // 透過傳入 thread 的參數,儲存 thread 的執行結果
        // 因為該參數是 array,傳入是 call by value
        // 會將 sum1[0] 的記憶體位址傳給該 method
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_param);
            Int32 []sum1 = { 0 };

            textBox1.AppendText("執行緒開始執行\r\n");
            thd.Start(sum1);
            textBox1.AppendText("使用Join()方法," + "" +
                "所以必須等待執行緒執行結束...\r\n");
            thd.Join();
            textBox1.AppendText("sum1=" + sum1[0].ToString() + 
                "\r\n");
        }
    }
}

thread 生命週期

Thread 提供 Abort(), Join(), Interrupt(), Sleep(), Suspend(), Resume(),其中 Suspend(), Resume() 已經建議不要使用。

Join() 會等待 thread 結束,呼叫 Join() 的 thread 會持續等待無法回應。

結束 Thread 的方法,是讓該 Thread 完工後自行結束,如果是持續工作的 Thread,就要用 Abort(),或是用全域變數判斷要不要繼續執行。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        bool fgDone_a, fgDone_b;
        int guess_a, guess_b;
        Thread th_a=null;
        bool fgRun;
        int num_a = 78, num_b=50;

        public Form1()
        {
            InitializeComponent();
        }

        // 亂數產生數字,直到該數字為 78
        // 結束時,設定 fgDone_a,並停止 timer1
        void count_a()
        {
            Random rd = new Random();
            try
            {
                while (guess_a != num_a)
                {
                    guess_a = rd.Next(1, 101);
                    Thread.Sleep(100);
                }

                fgDone_a = true;
            }
            catch (ThreadAbortException ex)
            {
                timer1.Enabled = false;
            }
            catch (ThreadInterruptedException ex)
            {
                timer1.Enabled = false;
            }
        }

        void count_b()
        {
            Random rd = new Random();

            while (guess_b != num_b && fgRun)
            {
                guess_b = rd.Next(1, 101);
                Thread.Sleep(100);
            }

            fgDone_b = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            textBox1.AppendText(guess_a.ToString() + "\r\n");
            if (fgDone_a)
            {
                timer1.Enabled = false;
                textBox1.AppendText("找到了\r\n");
            }
        }

        // 用 count_a 產生 Thread th_a
        // 同時啟動 timer1,timer1 會呼叫 timer1_Tick
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_a);

            th_a = thd;
            fgDone_a = false;
            guess_a = -1;
            thd.Start();
            timer1.Enabled = true;
        }

        // 呼叫 th_a.Abort()
        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("使用Abort()中止執行緒\r\n");
            th_a.Abort();
        }

        // 呼叫 th_a.Interrupt()
        private void button3_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("Interrupt()中斷執行緒\r\n");
            th_a.Interrupt();
        }

        // 用 count_b 產生 Thread th_b
        // 將全域變數 fgRun 設定為 true
        // 同時啟動 timer2,timer2 會呼叫 timer2_Tick
        private void button4_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_b);

            fgDone_b = false;
            guess_b = -1;
            fgRun = true;
            thd.Start();
            timer2.Enabled = true;
        }

        // 將全域變數 fgRun 設定為 false
        private void button5_Click(object sender, EventArgs e)
        {
            fgRun = false;
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            textBox2.AppendText(guess_b.ToString() + "\r\n");
            if (fgDone_b)
            {
                timer2.Enabled = false;
                if(!fgRun)
                    textBox2.AppendText("結束執行緒\r\n");
                else
                    textBox2.AppendText("找到了\r\n");
            }
        }

        // 關閉 Form1,要停止 th_a,fgRun 設定為 false
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if(th_a!=null)
                th_a.Abort();

            fgRun = false;
        }        
    }
}

Thead 存取表單控制項

Form UI 控制項是由 UI Thread 控制,如果自己產生的 Thread 要跨到 UI thread 存取控制項,會發生錯誤。

前面的例子,是在 Thread 裡面計算後,將過程記錄在全域變數中,然後在 UI Thread 以 Timer 定時將資料顯示在 UI 上,這種方法比較麻煩。

透過控制項的 InvokeRequired 屬性及 Invoke(),可讓 Thread 直接存取控制項。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {        
        delegate void SafeCall(string str);
        Thread []myThreads= { null, null };
        int sum = 0;

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl() //無參數
        {
            if (textBox1.InvokeRequired)
            {
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;
                MethodInvoker ivk = new MethodInvoker(safeControl);
                textBox1.Invoke(ivk, new object[] { });
            }
            else
                textBox1.AppendText("myThreads[1]: " + 
                    sum.ToString() + "\r\n");
        }

        void myFunc()
        {
            while (true)
            {                    
               safeControl();
               Thread.Sleep(500);
            }          
        }

        // 用 InvokeRequired 判斷 textBox1 存取權
        // 如果是 true,就表示現在是 UI thread 在控制
        // 就要由 textBox1 呼叫一次 SafeCall
        // false 就表示為外部 thread 存取,可直接使用 textBox1
        void safeControl_param(string str) //有參數
        {
            if (textBox1.InvokeRequired)
            {
                SafeCall ivk = new SafeCall(safeControl_param);
                textBox1.Invoke(ivk, new object[] { str });
            }
            else
                textBox1.AppendText(str + "\r\n");
        }

        // myThreads[0] 的 method
        // 呼叫 safeControl_param
        void myFunc_param()
        {
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);
                safeControl_param("myThreads[0]: " + num.ToString());
                Thread.Sleep(500);
            }                                
        }

        // 用 button1 產生 myThreads[0]
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd;

           // 如果 myThreads[0] 已經存在,就要停止 myThreads[0]
           // 用 myFunc_param 重新產生一個 myThreads[0],並啟動
           if (myThreads[0] != null)
            {
                myThreads[0].Abort();
                myThreads[0].Join();
            }
            thd = new Thread(new ThreadStart(myFunc_param));
            myThreads[0] = thd;

            thd.Start();
        }

        // 用 button2 產生 myThreads[1]
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[1] != null)
            {
                myThreads[1].Abort();
                myThreads[1].Join();
            }
            thd = new Thread(new ThreadStart(myFunc));
            myThreads[1] = thd;

            thd.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach(var item in myThreads)
                if (item != null)
                    item.Abort();           
        }        
    }
}

另一種寫法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread[] myThreads = { null, null };
        int sum = 0;

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl_param(string str) //有參數
        {
            textBox1.Invoke(new Action (() =>
            {
               textBox1.AppendText(str);
            }
            ));
        }

        // this.Invoke 配合 new Action 
        void myFunc_param()
        {
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);

                //也能寫成函式來來呼叫:safeControl_param(num.ToString());
                this.Invoke(new Action(() =>
                {
                    textBox1.AppendText("myThreads[0]: "+num.ToString()+"\r\n");
                }
             ));

            Thread.Sleep(500);
            }
        }

        void safeControl() //無參數
        {
            sum++;
            if (sum > int.MaxValue)
                sum = 0;

            this.Invoke((MethodInvoker)delegate 
            { textBox1.AppendText( "myThreads[1]: " +
                    sum.ToString() + "\r\n"); }
            );
        }

        void myFunc()
        {
            while (true)
            {
                ////此段程式也能寫成函式來來呼叫:safeControl();
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;

                this.Invoke((MethodInvoker)delegate
                {
                    textBox1.AppendText("myThreads[1]: " +
                          sum.ToString() + "\r\n");
                }
                );

                Thread.Sleep(500);
            }
        }


        // 有參數的 myFunc_param Thread
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[0] != null)
            {
                myThreads[0].Abort();
                myThreads[0].Join();
            }
            thd = new Thread(new ThreadStart(myFunc_param));
            myThreads[0] = thd;

            thd.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (var item in myThreads)
                if (item != null)
                    item.Abort();
        }

        //  沒有參數的 myFunc Thread
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[1] != null)
            {
                myThreads[1].Abort();
                myThreads[1].Join();
            }
            thd = new Thread(new ThreadStart(myFunc));
            myThreads[1] = thd;

            thd.Start();
        }
    }
}

第三種寫法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        delegate void SafeCall(string str);
        Thread []myThread= { null, null };
        int sum;

        public Form1()
        {
            InitializeComponent();
        }

        void myFunc2()
        {
            MethodInvoker ivk = new MethodInvoker(safeControl);

            while (true)
            {
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;
                this.Invoke(ivk, new object[] { });
                Thread.Sleep(500);
            }
        }

        void safeControl() //無參數
        {
            textBox1.AppendText(sum.ToString() + "\r\n");
            label1.Text = sum.ToString() ;

        }

        void myFunc1()
        {
            SafeCall ivk = new SafeCall(safeControl_param);
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);
                this.Invoke(ivk, new Object[] { num.ToString()+"\r\n" });
                Thread.Sleep(500);
            }
        }

        private void safeControl_param(string str)
        {
            textBox1.AppendText(str);
            label1.Text = str;

        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(myFunc1);
            myThread[0] = thd;

            thd.Start();

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (var item in myThread)
                if (item != null)
                    item.Abort();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(myFunc2);
            myThread[1] = thd;

            sum = 0;
            thd.Start();
        }
    }
}

執行緒同步

如果有多個 thread 會同時存取相同的資源,會造成內容一致性的問題。有四個方法,可處理 synchronization 問題

  • synchronized code region -> 最常用

    當 thread A 執行時,thread B 必須等待 A 完成後,才能執行該區塊

  • manual synchronization

    使用 .Net Framework 的 Mutex, Semaphore, EventWaitHandle, AutoResetEvent, ManualResetEvent

  • synchronized contexts

    使用 SynchronizationAttribute 設定 ContextBoundObject

    當物件進入或離開由 ContextBoundObject 定義的內容時,會強制執行規則

  • 使用 System.Collections.Concurrent 的集合與類別

Critical Section: thread 存取的共用資源(物件、資料、變數、設備),同一時間只有一個 thread 能使用

lock()

互斥鎖: public object locker = new object();

lock(locker) 以 locker 鎖定 critical section

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread thd1=null, thd2=null;
        object locker = new object();
        bool fg1 = false, fg2=false;

        public Form1()
        {
            InitializeComponent();
        }

        // 當 Form1 載入時,就啟動兩個 thread
        private void Form1_Load(object sender, EventArgs e)
        {
            thd1 = new Thread(func1);
            thd2 = new Thread(func2);

            thd1.Start();
            thd2.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (thd1 != null)
                thd1.Abort();

            if (thd2 != null)
                thd2.Abort();
        }

        // 無窮迴圈
        // 當 fg1 為 true,就 lock locker 物件,執行 critical section 區塊
        // 完成後將 fg1 改為 false
        void func1()
        {
            while(true)
            {
                if(fg1)
                    lock(locker)
                    {
                        for(int i=0;i<10;i++)
                        {
                            textBox1.Invoke(new Action(() =>
                            {
                                textBox1.AppendText("Thread 1\r\n");
                            }));
                            Thread.Sleep(500);
                        }
                        fg1 = false;
                    }
            }
        }

        void func2()
        {
            while (true)
            {               
                if (fg2)
                    lock (locker)
                    {
                        textBox1.Invoke(new Action(() =>
                        {
                            textBox1.AppendText("Thread 2\r\n");
                        }));
                        fg2 = false;
                    }
            }        
        }

        // 將 fg1 改為 true
        private void button1_Click(object sender, EventArgs e)
        {                        
            if (!fg1)
                fg1 = true;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (!fg2)
                fg2 = true;
        }     
    }
}

Monitor

method 說明
Enter(a, [b]) 取得並鎖定互斥鎖 a,a為 Object,b 為 Boolean。當 b 為 true,表示已經取得互斥鎖 a,否則為 false
Exit(a) 釋放互斥鎖 a
IsEntered(a) 判斷 thread 是否已經取得互斥鎖 a
Pulse(a) 通知等候的 thread,互斥鎖 a 已改變狀態
PulseAll(a) 通知等候 queu 的所有 threads,互斥鎖 a 已改變狀態
TryEnter(a,b,c) 嘗試在時間 b 取得互斥鎖 a,並回傳結果 c。
a: Object, b: Int32 or TimeSpan, c: Boolean
TryEnter(a[,b]) 嘗試在時間 b 取得互斥鎖 a。
a: Object, b: Int32 or TimeSpan
TryEnter(a[,b]) 嘗試取得互斥鎖 a,並回傳結果 b。
a: Object, b: Boolean
Wait(a[,b[,c]]) 釋放互斥鎖 a ,並在時間 b 內,嘗試重新取得互斥鎖 a。如果無法取得,就會進入等候 queue
a: Object, b: Int32 or TimeSpan, c: Boolean

Enter, Exit 要搭配使用,否則會造成 a 無法釋放或 deadlock

Pulse() 只能被正在鎖定互斥鎖的 thread 呼叫

critical section

Monitor.Enter(locker);
.
.
.
Monitor.Exit(locker);
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread thd1 = null, thd2 = null;
        object locker = new object();
        bool fg1 = false, fg2 = false;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            thd1 = new Thread(func1);
            thd2 = new Thread(func2);

            thd1.Start();
            thd2.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (thd1 != null)
                thd1.Abort();

            if (thd2 != null)
                thd2.Abort();
        }

        void func1()
        {
            while (true)
            {
                if (fg1)                    
                {
                    Monitor.Enter(locker);
                    for (int i = 0; i < 10; i++)
                    {
                        textBox1.Invoke(new Action(() =>
                        {
                                textBox1.AppendText("Thread 1\r\n");
                        }));
                        Thread.Sleep(500);
                    }
                    Monitor.Exit(locker);
                    fg1 = false;
                }
            }
        }

        void func2()
        {
            while (true)
            {
                if (fg2)                    
                {
                    Monitor.Enter(locker);
                    textBox1.Invoke(new Action(() =>
                    {
                        textBox1.AppendText("Thread 2\r\n");
                    }));
                    Monitor.Exit(locker);
                    fg2 = false;
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (!fg1)
                fg1 = true;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (!fg2)
                fg2 = true;
        }
    }
}

Semaphore

5 人去店裡繳費,但只有 3 個櫃檯

一般來說臨櫃劉成為:取號碼牌、有空的櫃檯時叫號、沒有空的櫃檯就等待

Semaphore 提供機制協調多個 thread 的同步處理

// 有三個號誌數量,最多可接受3個 thread 要求號誌 
Semaphore smphore = new Semaphore(3,3);


// 有0個號誌數量,最多可接受3個 thread 要求號誌
// 一開始,所有 thread 都在等待 
Semaphore smphore = new Semaphore(0,3);

// 釋放 3 個號誌
smphore.Release(3);

// 沒有參數,表示釋放先前取得的號誌
smphore.Release();
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Semaphore smphore;
        Thread[] thds=new Thread[5];
        delegate void SafeCall(string str);

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl(string str)
        {
            textBox1.AppendText(str);
        }

        void func(object param)
        {
            int no = (int)param;
            string str;
            SafeCall ivk = new SafeCall(safeControl);

            str = String.Format("第{0}位在排隊...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str});

            // 所有 threads 會停在這裡等待 semaphore
            smphore.WaitOne();

            str = String.Format("第{0}位正在繳費...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str });
            Thread.Sleep(1000);

            str = String.Format("第{0}位繳費結束...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str });
            smphore.Release();
            // 結束時,釋放 semaphore
        }        

        private void Form1_Load(object sender, EventArgs e)
        {
            // 載入時,產生 semaphore
            smphore = new Semaphore(0, 3);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 產生並啟動 5 個 threads
            for (int i = 0; i < thds.Length; i++)
                thds[i] = new Thread(
                    new ParameterizedThreadStart(func));

            for (int i = 0; i < thds.Length; i++)
                thds[i].Start(i + 1);

            textBox1.AppendText("尚未開始營業...\r\n");
            Thread.Sleep(3000);

            textBox1.AppendText("開始營業...\r\n");
            smphore.Release(3);
        }
    }
}

References

博客來-C#程式設計從入門到專業(下):職場C#進階應用技術

沒有留言:

張貼留言