[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);
}
}
}
}
輸出結果
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);
}
}
}
}
輸出結果
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); // 暫停執行緒
}
}
}
}
輸出結果
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);
}
}
}
輸出結果
sample5
藉由 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);
}
}
}
}
輸出結果
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);
}
}
輸出結果
另外:可以使用 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 + " 終止");
}
}
}
輸出結果
Refrence
- System.Threading 命名空間
- Thread 類別
- Thread 成員
- ThreadStart 委派
- ParameterizedThreadStart 委派
- Monitor 類別
- Monitor 成員
- 同步處理原始物件概觀
- 執行緒處理物件和功能
- Thread..::.Interrupt 方法
三小俠 小弟獻醜,歡迎指教