[C#] 執行緒 (Thread)(一)

  • 95883
  • 0

[C#] 執行緒 (一)

Introduction

處理序代表一個正在執行的應用程式,而執行緒則是處理序內部,任何一段可執行的程式碼。

一個處理序可能同時存在著一個以上的執行緒,你可以將其視為處理序所執行的工作。

多執行緒通常被應用在以下幾種狀況:

  • 耗時的運算工作:例如複雜的演算法,建立新的執行緒物件,可以讓程式進行運算同時,也可以指定其他工作,避免程式呆滯。
  • 等待回應訊息:例如讀取或是下載大量的檔案,當你等待這些工作完成之前,可以讓處理器執行其他工作,一旦接收到工作

                                     完成的回應事件時,才回來進行未完成的工作。

若是要使用執行緒相關物件,需要引入 System.Threading 命名空間

 

Example

sample1

建立執行緒,其中 ThreadStart 是一個委派型別,可以指向沒有參數沒有回傳值得方法。

class Program {
        static void Main(string[] args) {
            Program oProgram = new Program();
            oProgram.Start();
            Console.ReadKey();
        }

        private void Start() {
            //建立一個執行緒,並且傳入一個委派物件 ThreadStart,並且指向 PrintOddNumber 方法。            
            Thread oThreadA = new Thread(new ThreadStart(PrintOddNumber));
            oThreadA.Name = "A Thread";

            //建立一個執行緒,並且傳入一個委派物件 ThreadStart,並且指向 PrintNumber 方法。
            Thread oThreadB = new Thread(new ThreadStart(PrintNumber));
            oThreadB.Name = "B Thread";

            //啟動執行緒物件
            oThreadA.Start();
            oThreadB.Start();
        }

        //印出奇數
        private void PrintOddNumber() {
            for (int n1 = 1; n1 < 10; n1++) {
                if (n1 % 2 != 0) {
                    Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
                }
            }
        }
        
        //印出偶數
        private void PrintNumber() {
            for (int n1 = 1; n1 < 10; n1++) {
                if (n1 % 2 == 0) {
                    Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
                }
            }
        }
    }

輸出結果

2009-12-19_013727

 

sample2

建立可以傳入參數的執行緒 ,ParameterizedThreadStart

public class Program {
        public static void Main(string[] args) {
            Program oProgram = new Program();
            oProgram.Start();
            Console.ReadKey();
        }

        private void Start() {
            //建立一個執行緒,並且傳入一個委派物件 ParameterizedThreadStart,'
            //並且設定指向 PrintOddNumber 方法。               
            Thread oThreadA = new Thread(new ParameterizedThreadStart(PrintOddNumber));

            //設定執行緒的 Name
            oThreadA.Name = "A Thread";

            //建立一個執行緒,並且傳入一個委派物件 PrintNumber,'
            //並且設定指向 PrintOddNumber 方法。               
            Thread oThreadB = new Thread(new ParameterizedThreadStart(PrintNumber));

            //設定執行緒的 Name
            oThreadB.Name = "B Thread";

            //啟動執行緒物件,並且傳入參數
            oThreadA.Start(10);
            oThreadB.Start(10);
        }

        //印出奇數
        private void PrintOddNumber(object value) {
            int Number = Convert.ToInt32(value);
            for (int n1 = 1; n1 <= Number; n1++) {
                if (n1 % 2 != 0) {
                    Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
                }
            }
        }

        //印出偶數
        private void PrintNumber(object value) {
            int Number = Convert.ToInt32(value);
            for (int n1 = 1; n1 <= Number; n1++) {
                if (n1 % 2 == 0) {
                    Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
                }
            }
        }
    }

輸出結果

2009-12-19_013727

 

sample3

暫停執行緒使用 Sleep()

 class Program {
        static void Main(string[] args) {
            Program oProgram = new Program();
            oProgram.Start();
            Console.ReadKey();
        }
        private void Start() {
            //建立委派物件並指向 PrintNumber 方法
            ThreadStart myThreadStart = new ThreadStart(PrintNumber);
            //建立執行緒物件
            Thread myThread = new Thread(myThreadStart);
            myThread.Start(); //啟動執行緒
        }
        public void PrintNumber() {
            for (int n1 = 0; n1 <= 10; n1++) {
                Console.WriteLine
                    (Thread.CurrentThread.Name +
                    "迴圈次數" + n1 + " 次");
                if (n1 == 5) {
                    Console.WriteLine(" 執行緒暫停 5 秒鐘 !!");
                    Thread.Sleep(5000);   // 暫停執行緒 
                }
            }
        }
    }

輸出結果

2009-12-19_015711

 

sample4

執行緒使用 Join()

當執行緒本身無法決定暫停多久,必須等到其他的執行緒完成,才能繼續剩下的工作,使用這個方法,

會封鎖目前的執行緒,直到引用這個方法的執行緒執行完成之後,再進行未完成的工作。

 class Program {
        //欄位
        private Thread ThreadA;
        private Thread ThreadB;

        static void Main(string[] args) {
            //這邊有三個 thread :  本身應用程式的主執行緒,
            //還有另外建立的兩條執行緒 ThreadA、 ThreadB

            Program oProgram = new Program();
            oProgram.Start();
            Console.WriteLine(" 暫停主執行緒 !!");
            //暫停主執行緒,等待執行緒 A 工作完畢
            oProgram.ThreadA.Join();
            Console.WriteLine(" 執行緒工作完成 !!");
            Console.ReadKey();
        }


        private void Start() {
            //建立執行緒 A
            ThreadA = new Thread(new ThreadStart(PrintNumber));
            ThreadA.Name = "A Thread";
            //建立執行緒 B
            ThreadB = new Thread(new ThreadStart(JoinPrintNumber));
            ThreadB.Name = "B Thread";

            //啟動執行緒
            ThreadA.Start();
            ThreadB.Start();

        }
        private void PrintNumber() {
            for (int n1 = 1; n1 <= 10; n1++) {
                Console.WriteLine
                  (Thread.CurrentThread.Name +
                  " 迴圈: " + n1 + " 次");
                Thread.Sleep(1000);
                if (n1 == 5) {
                    Console.WriteLine("暫停執行緒 {0} ", Thread.CurrentThread.Name);
                    //暫停目前執行緒,等待 ThreadB 執行完
                    ThreadB.Join();
                }

            }
        }
        private void JoinPrintNumber() {
            for (int n1 = 11; n1 <= 20; n1++) {
                Console.WriteLine
                  (Thread.CurrentThread.Name +
                  " 迴圈開始執行迄今第 " + n1 + " 次");
                Thread.Sleep(1000);
            }
        }
    }

輸出結果

2009-12-19_021406

 

sample5

執行緒同步化,避免資源存取衝突 ,使用 Lock

藉由 Lock 敘述句完成執行緒同步作業,用以控制程式上某一段資源,這時,其他的執行緒沒權限

可以存取這份資源。

class Program {
        private int addSum;

        static void Main(string[] args) {
            Program oProgram = new Program();            
            //建立執行緒陣列
            Thread[] threads = new Thread[3];
            //依序設定陣列內容
            for (int n1 = 0; n1 < 3; n1++) {
                Thread myThread = new Thread(new ThreadStart(oProgram.DoSum));
                threads[n1] = myThread;
                threads[n1].Name = "執行緒" + n1;
            }
            //依序啟動執行緒
            for (int n1 = 0; n1 < 3; n1++) {
                threads[n1].Start();
            }
            Console.ReadKey();
        }

        void DoSum() {
            for (int n1 = 0; n1 < 7; n1++) {
                //當目前執行緒執行這個方法時,會鎖住資源,其他執行緒無法存取,直到該執行緒工作完成
                lock (this) {//this 表示,目前執行緒所在的類別,也就是鎖住這個類別的資源
                    addSum += 2;
                    Thread.Sleep(1);
                    Console.WriteLine
                      (Thread.CurrentThread.Name + ",執行第 " + n1 + " 次,addSum =" + addSum);
                }
            }
        }
    }

輸出結果

2009-12-19_023926

 

sample5

Monitor 類別,與 Lock 不同的是, Monitor 類別進一步提供阻斷或是繼續特定執行緒的執行動作,讓資源的存取能夠

更進一步獲得控制,Monitor 提供一些靜態方法。

  • Enter :取得指定物件的獨佔鎖定,通常我們會直接傳入 this 關鍵字,表示監控目前產生執行緒的物件。
  • Wait:多載。 釋出物件的鎖並且封鎖目前的執行緒,直到這個執行緒重新取得鎖定為止。
  • Pulse:通知等候佇列中的執行緒,鎖定物件的狀態有所變更,總是會恢復第一個被暫停的執行緒。
  • Exit:釋出指定物件的獨佔鎖定。

完整成員方法請參考 http://msdn.microsoft.com/zh-tw/library/system.threading.monitor_members%28VS.80%29.aspx

 

class Program {
        private int dataSum = 0;
        private int dataOutput = 0;

        static void Main(string[] args) {
            Program oProgram = new Program();
            oProgram.Start();
            Console.Write("完成");
            Console.ReadKey();
        }

        void Start() {
            //建立執行緒
            Thread TA = new Thread(new ThreadStart(DataHandle));
            Thread TB = new Thread(new ThreadStart(DataPrint));
            TA.Name = "執行緒 A ";
            TB.Name = "執行緒 B ";
            //啟動執行緒
            TB.Start();
            TA.Start();
            //暫停主執行緒,等待 A、B 完成工作
            TA.Join();
            TB.Join();
        }

        void DataHandle() {
            //監控進入點
            Monitor.Enter(this);

            for (int n1 = 0; n1 < 10; n1++) {

                //若 dataOutput 等於 10,則鎖定目前的執行緒,並將這個執行緒鎖定的資源釋放
                if (dataOutput == 5) Monitor.Wait(this);

                dataOutput++;
                Console.WriteLine
                    (Thread.CurrentThread.Name +
                    " 正在處理第 " + dataOutput + "筆資料 !! ");
                Thread.Sleep(100);
                if (dataOutput == 5) {
                    //總是會恢復第一個被暫停的執行緒 TB 會被叫醒
                    Monitor.Pulse(this);
                    Console.WriteLine();
                }
            }
            //結束監控
            Monitor.Exit(this);
        }

        void DataPrint() {
            //監控進入點
            Monitor.Enter(this);
            do {
                //若 dataOutput 等於 0,則鎖定目前的執行緒,並將這個執行緒鎖定的資源釋放
                //所以 TB 一開始就會暫停工作
                if (dataOutput == 0) Monitor.Wait(this);

                Console.Write
                    (Thread.CurrentThread.Name +
                    " 正在列印第 " + dataOutput + "筆資料 !! ");
                Thread.Sleep(100);
                dataOutput--;
                dataSum++;
                Console.WriteLine
                    ("\t總處理資料筆數 {0} !!", dataSum);
                if (dataOutput == 0) {
                    //總是會恢復第一個被暫停的執行緒 TA 會被叫醒
                    Monitor.Pulse(this);
                    Console.WriteLine();
                }
            } while (dataSum < 10);
            //結束監控
            Monitor.Exit(this);
        }
    }

輸出結果

2009-12-19_115435

另外:可以使用 Lock 敘述,來取代 Monitor.Entry(this) 與 Monitor.Exit(this) ,來限制程式碼與資料的存取。

 

sample6

終止執行緒,可以使用 Interrupt 方法,這個方法會阻斷處於 WaitSleepJoin 狀態下的執行緒。

msdn :

如果這個執行緒目前沒有封鎖於等候、休眠或聯結 (Join) 狀態中,就會在下一次要開始封鎖時被插斷。

插斷的執行緒中會擲回 ThreadInterruptedException,但必須等到執行緒封鎖後才會擲回。如果執行緒一直沒有封鎖,就永遠不會擲回此例外狀況,因此執行緒完成時可能完全沒有插斷。

 

class Program {
        long fSum1 = 0;
        long fSum2 = 2;
        Thread Threading1;
        Thread Threading2;
        static void Main(string[] args) {
            Program oProgram = new Program();
            oProgram.Start();
            Console.ReadKey();
        }

        private void Start() {
            Threading1 = new Thread(new ThreadStart(FibonnacciSeries1));
            Threading1.Name = "ThreadA";

            Threading2 = new Thread(new ThreadStart(FibonnacciSeries2));
            Threading2.Name = "ThreadB";

            Threading1.Start(); //啟動第一個執行緒
            Threading2.Start(); //啟動第二個執行緒 

            Threading1.Join();
            Threading2.Join();
        }
        private void FibonnacciSeries1() {
            try {
                for (int n1 = 0; n1 < 10; n1++) {
                    Thread.Sleep(1000);
                    fSum1 += n1;
                    if (n1 > 5) {
                        //終止執行緒
                        Threading1.Interrupt();
                    }
                    Console.WriteLine(Thread.CurrentThread.Name + " : " + " 目前總合為  = " + fSum1);
                }
            }      //捕捉例外             
            catch (ThreadInterruptedException) {
                Console.WriteLine(Thread.CurrentThread.Name + " 終止");
            }
        }
        private void FibonnacciSeries2() {
            try {
                for (int n1 = 0; n1 < 10; n1++) {
                    Thread.Sleep(1000);
                    fSum2 += n1;
                    Console.WriteLine(Thread.CurrentThread.Name + " : " + " 目前總合為  = " + fSum2);
                }
            }
            catch (ThreadInterruptedException) {
                Console.WriteLine(Thread.CurrentThread.Name + " 終止");
            }
        }   
    }

輸出結果

2009-12-19_122349

 

Refrence

三小俠  小弟獻醜,歡迎指教