須先引入using System.Threading;
便可在程式中使用Thread.Sleep(毫秒數);
Thread.Sleep(0) 表示掛起0毫秒但在MSDN說明中指出:指定零 (0) 以指示應掛起此線程以使其他等待線程能夠執行。Thread.Sleep(0) 並非是真的要線程等待0毫秒,意義在於這次調用Thread.Sleep(0)的當前線程確實的被凍結了一下,讓其他線程有機會優先執行。 Thread.Sleep(0) 是你的線程暫時放棄cpu,也就是釋放一些未用的時間片給其他線程或進程使用,就相當於一個讓位動作。
便可在程式中使用Thread.Sleep(毫秒數);
Thread.Sleep(0) 表示掛起0毫秒但在MSDN說明中指出:指定零 (0) 以指示應掛起此線程以使其他等待線程能夠執行。Thread.Sleep(0) 並非是真的要線程等待0毫秒,意義在於這次調用Thread.Sleep(0)的當前線程確實的被凍結了一下,讓其他線程有機會優先執行。 Thread.Sleep(0) 是你的線程暫時放棄cpu,也就是釋放一些未用的時間片給其他線程或進程使用,就相當於一個讓位動作。
進階:
執行緒可分為前景執行緒與背景執行緒兩種,在預設的狀況下Thread是屬於前景執行緒也就
前景是Thread.IsBackground=false
背景是Thread.IsBackground=true
差別在於若主程序已下達中止工作命令了,有任一前景執行緒尚未完成工作,程序不會立即中止,需待前景執行緒完成工作後才會終止。
反之,背景執行緒不管工作有沒有完成,一但收到中止命令,馬上就停下手邊的工作中止工作。
我們來用程式碼觀察其結果,程式碼出自http://msdn.microsoft.com/zh-tw/library/system.threading.thread.aspx
執行結果,當我按下任意鍵了,程序卻沒停止還在繼續工作
進階2 轉錄自 http://huan-lin.blogspot.com/2013/05/csharp-notes-multithreading-2.html
有待消化整理
C# 學習筆記:多執行緒 (2) - 分道揚鑣
摘要:C# 非同步程式設計的學習筆記之二,包括:建立與啟動執行緒、等待與暫停執行緒、共享變數、鎖定等議題。
續上集,這次開始寫點程式碼來建立執行緒。
建立執行緒來進行非同步運算
本節將介紹如何利用 System.Threading.Thread 類別來建立執行緒,以進行非同步運算。不過,這裡只是讓你知道有這些雞肋用法,並非建議你一蓋採用。事實上,Windows 市集應用程式無法使用本節介紹的程式寫法,因為它根本沒有 Thread 類別可用。比較好的作法,是盡量透過執行緒集區(thread pool)來處理背景運算的工作,這個部分(應該)會在將來進一步說明。
執行緒集區與執行緒耗盡
.NET CLR 實作了集區(pool)的概念,讓應用程式可以將完成任務的執行緒丟進集區裡面待命,等到有其他工作需要非同步執行,便可直接從集區取出執行緒,並將工作派給它執行。如此一來,不但省去了頻繁建立和釋放執行緒的時間成本,也因為執行緒集區是由系統來管理,系統更能針對整體執行環境的狀況來調整相關參數(例如每條執行緒要分配多少執行時間),而開發人員也能夠更專注於實現應用程式的邏輯,而不是埋首與底層細節奮戰。
CLR 管理的執行緒集區有兩種: 工作執行緒集區(worker thread pool)和輸入/輸出執行緒集區(I/O thread pool)。兩種集區裡面的執行緒是同樣東西;之所以分成兩個集區,主要是希望應用程式依實際用途來選擇適當的集區,以免經常發生執行緒耗盡(thread starvation)的情形。
那麼,執行緒耗盡又是什麼意思呢?
正在執行任務的執行緒,就像忙線中的客服人員,必須等到掛完電話,才有辦法繼續接聽下一通電話。然而,當大量工作需求同時產生(許多用戶同時打電話給同一家公司的客服專線),執行緒集區裡面的可用執行緒的數量就會迅速減少,甚至出現完全沒有執行緒可用的情況(所有客服人員現在全部忙線中,若要等待,請按米字鍵…)。這種狀況叫做「執行緒耗盡」,它會嚴重影響應用程式的效能與延展性。
相較於執行緒集區這種從一個共用的池子裡面取用執行緒的作法,「自行建立執行緒」則意味著建立新的執行緒來專門負責處理特定工作,所以有時候我們也說這種作法是「建立專屬的執行緒(dedicated thread)」。
當你碰到以下幾種特殊場合,才應該自行建立專屬的執行緒來處理特定的運算工作:
- • 你希望某些執行緒擁有特殊優先權。在預設情況下,執行緒集區裡面的執行緒都是「正常」優先權。如果想要讓某執行緒擁有特權,可以個別建立執行緒並修改其優先權。但一般不建議這麼做就是了。
- • 你希望某些執行緒以前景執行緒的方式運作,以避免工作還沒完成,應用程式就被使用者或其他程序關閉。執行緒集區裡面的執行緒永遠都是背景執行緒,它們有可能還沒完成任務就被 CLR 結束掉。執行緒集區裡面的執行緒
- • 執行緒所負責的工作需要大量運算,而且需要花很長的時間才能執行完畢。碰到這種情況,自行建立執行緒可能會比使用執行緒集區來的有效率,因為這樣可以省去執行緒集區的一些處理邏輯,例如何時該建立額外的執行緒。
- • 執行緒開始工作後,你可能需要在某些情況下提前終止執行緒(透過呼叫 Thread 類別的 Abort 方法)。
接著就來看一些範例程式。
建立與啟動執行緒
底下是個測試多執行緒的簡單範例,示範如何建立一個執行緒來執行某件背景工作。有個名詞得先說一下:負責執行背景工作的執行緒又稱為「工作執行緒」(worker thread)。之所以不說「背景執行緒」,是為了避免跟執行緒集區有關的前景、背景執行緒概念混淆。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using System;
using System.Threading;
class Program
{
static void Main(strin g[] args)
{
Thread t1 = new Thread(MyBackgroundTask);
t1.Start();
for (int i = 0; i < 500; i++)
{
Console.Write(".");
}
}
static void MyBackgroundTask()
{
for (int i = 0; i < 500; i++)
{
Console.Write("[" + Thread.CurrentThread.ManagedThreadId + "]");
}
}
}
|
程式說明:
- • 使用 System.Threading.Thread 類別來建立執行緒物件,同時將一個委派方法 MyBackgroundTask 傳入建構函式。這個委派方法將於該執行緒開始於背景運行時被自動呼叫。
- • 呼叫執行緒物件的 Start 方法,令執行緒開始運行,亦即在這個工作執行緒中呼叫 MyBackgroundTask 方法。
- • Main 函式開始一個迴圈,持續輸出 “.”。這只是為了識別哪些文字是由主執行緒輸出,哪些是由工作執行緒輸出。
- • MyBackgroundTask 函式也有一個迴圈,持續輸出目前執行緒的編號。
下圖為此範例程式的執行結果:
從輸出結果可以看得出來,主執行緒跑了一段時間,切換至我們另外建立的工作執行緒。工作執行緒也同樣跑了一段時間之後,又切回主執行緒,如此反覆切換,直到兩個執行緒的迴圈結束為止。
建立 Thread 物件時,傳入建構函式的委派有兩種版本。一種是 ThreadStart,另一種是 ParameterizedThreadStart。以下是這兩種委派型別的宣告:
public delegate void ThreadStart();
public delegate void ParameterizedThreadStart(Object obj);
前述範例使用的是第一種,也就是不需要傳入參數的 ThreadStart 委派型別。如果在啟動工作執行緒時需要額外傳入一些資料,就可以使用第二種委派型別: ParameterizedThreadStart。參考以下範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
using System;
using System.Threading;
class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(MyBackgroundTask);
Thread t2 = new Thread(MyBackgroundTask);
Thread t3 = new Thread(MyBackgroundTask);
t1.Start("X");
t2.Start("Y");
t3.Start("Z");
for (int i = 0; i < 500; i++)
{
Console.Write(".");
}
}
static void MyBackgroundTask(object param)
{
for (int i = 0; i < 500; i++)
{
Console.Write(param);
}
}
}
|
程式說明:
- • 首先建立三個執行緒物件,而且這三個執行緒都會執行同一項任務:MyBackgroundTask。
- • MyBackgroundTask 方法需要傳入一個 object 型別的參數,而此參數的值是在啟動執行緒時傳入。在啟動三個執行緒物件時,我分別傳入了 “X”、”Y”、”Z”,以便從輸出結果中觀察各執行緒輪流切換的情形。
執行結果:
等待與暫停執行緒
Thread 類別有個 IsAlive 屬性,代表執行緒是否正在運行。一旦呼叫執行緒物件的 Start 方法令它開始執行,其 IsAlive 屬性值就會等於 true,直到該執行緒的委派方法執行完畢,那條執行緒便隨之結束。因此,如果想要等待某執行緒的工作執行完畢才繼續處理其他工作,用一個迴圈來持續判斷執行緒物件的 IsAlive 屬性就能辦到。
還有一個更簡單的作法可以等待執行緒結束:呼叫 Thread 物件的 Join 方法。以下程式片段修改自上一個範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
static void Main(string[] args)
{
Thread t1 = new Thread(MyBackgroundTask);
Thread t2 = new Thread(MyBackgroundTask);
Thread t3 = new Thread(MyBackgroundTask);
t1.Start("X");
t2.Start("Y");
t3.Start("Z");
t1.Join();
t2.Join();
t3.Join();
for (int i = 0; i < 500; i++)
{
Console.Write(".");
}
}
|
這次我在 Main 函式中加入了三行程式碼,分別呼叫三個執行緒物件的 Join 方法。這會令主執行緒依序等待 t1、t2、t3 執行完畢之後才繼續跑底下的迴圈。執行結果如下圖:
此外,有時候我們會需要讓某執行緒稍事休息,此時可呼叫 Thread 類別的靜態方法 Sleep。此方法會令目前所在的執行緒休息一段指定的時間,時間單位是毫秒(millisecond)。範例:略。
共享變數
執行緒之間可以共享變數,有時候也的確需要這麼做。多條執行緒之間共享同一個變數時,如果都只是讀取變數值,並不會有問題。但如果執行緒會去修改共享變數的值,那就得運用一些技巧來避免數值錯亂的情形。看看底下這個範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
class Program
{
static void Main(string[] args)
{
new SharedStateDemo().Run();
Console.ReadLine();
}
}
public class SharedStateDemo
{
private int itemCount = 0; // 已加入購物車的商品數量。
public void Run()
{
var t1 = new Thread(AddToCart);
var t2 = new Thread(AddToCart);
t1.Start(300);
t2.Start(100);
}
private void AddToCart(object simulateDelay)
{
itemCount++;
/*
* 用 Thread.Sleep 來模擬這項工作所花的時間,時間長短
* 由呼叫端傳入的 simulateDelay 參數指定,以便藉由改變
* 此參數來觀察共享變數值的變化。
*/
Thread.Sleep((int)simulateDelay);
Console.WriteLine("Items in cart: {0}", itemCount);
}
}
|
程式說明:
- • Main 函式會建立 SharedStateDemo 物件並呼叫其 Run 方法。此範例的重點都在 SharedStateDemo 類別裡面,示範的情境為購物車。
- • SharedStateDemo 類別有一個整數欄位:itemCount,代表已加入購物車的商品數量。此變數將作為執行緒之間共享的變數。
- • SharedStateDemo 類別的 Run 方法會建立兩條執行緒,它們的工作都是呼叫 AddCart 方法,代表「加入購物車」的動作。
- • AddCart 方法需要傳入一個參數,用來模擬每一次加入購物車的動作需要花多少時間。從 Run 方法的程式碼可以看得出來,我刻意讓第一條執行緒花比較多時間(延遲 300 毫秒)。
執行結果:
如果 t1 和 t2 這兩條執行緒是依照它們啟動的順序先後完成任務,執行結果的第一列顯示地購物車商品數量應為 1,第二列的數量才是 2。可是現在卻全都是 2,這是因為 t1 先啟動,進入 AddCart 函式之後,把 itemCount 加一,然後進入一段模擬長時間工作的延遲(300ms)。此時 t2 也已經啟動了,也把 itemCount 加一了(其值為 2),然後也進入一段延遲(100ms)。但由於 t2 的延遲時間較短,比 t1 更快執行完畢(後發而先至),因此執行結果畫面中的第一列文字其實是由執行緒 t2 輸出的。接下來,t1 也跑完了,但此時的 itemCount 已經被 t2 改成了 2,所以輸出的結果自然就一樣了。
有時候,這種多條執行緒共同修改一個變數的情況可能會導致嚴重問題。比如說,當應用程式正在計算某員工的薪資,才處理到一半,還沒算完呢,又有其他執行緒修改了共享的薪資計算參數,可能原本的計算結果應該是 63,000,結果卻成了 59,000。接著就來看看如何解決這個問題,讓此範例的執行結果顯示的商品數量變成先 1 後 2,而不是兩次都輸出 2。
其實在我的機器上,即使呼叫 t1.Start() 時傳入 1(僅延遲 1 毫秒),輸出結果仍舊相同,並不因為 t1 的模擬延遲時間縮短成 1 毫秒而產生先 1 後 2 的結果。我想這是因為我的機器有多核心 CPU,於是 t1 才剛啟動,將 itemCount 遞增為 1,此時 t2 其實也已經(由另一個 CPU 核心)啟動了,itemCount 便遞增為 2。
不過,如果改成 t1.Start(0),亦即令 t1 模擬延遲的時間為 0 毫秒,結果就會變成先 1 後 2 了。這是因為 Thread.Sleep(0) 完全沒有延遲的作用,故來得及在其他執行緒進入該程式區塊之前完成工作。
鎖定
剛才展示的多執行緒修改同一變數所衍生之變數值錯亂的問題,有點像是很多人同時伸手搶一塊餅──很容易把餅給抓爛了。解決方法很簡單:排隊。也就是說,原本以非同步執行的各條執行緒,碰到了要修改共享變數的時候,都要乖乖排隊,一個做完了才換下一個。這等於是暫時切換成同步執行的方式,如同在八線道的公路某處設下關卡,將道路限縮成單線道,只許一輛汽車通行;等車輛駛出關卡,前方又是一片開闊,任憑奔馳。
我們可以利用獨佔鎖定(exclusive lock)的技巧來建立這道關卡,迫使各執行緒存取共享變數時乖乖排隊,亦即令它們同步化(synchronization)。這裡要示範的是以 C# 的 lock 敘述來建立獨佔鎖定的程式區塊。我們只要稍微修改上一個範例的 SharedStateDemo 類別,輸出結果就會不同。底下是修改後的程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class SharedStateDemo
{
private int itemCount = 0;
private object locker = new Object(); // 用於獨佔鎖定的物件
public void Run()
{
var t1 = new Thread(AddToCart);
var t2 = new Thread(AddToCart);
t1.Start(300);
t2.Start(100);
}
private void AddToCart(object simulateDelay)
{
Console.WriteLine("Enter thread {0}", // 顯示目前所在的執行緒編號
Thread.CurrentThread.ManagedThreadId);
lock (locker) // 利用 locker 物件來鎖定程式區塊
{
itemCount++;
Thread.Sleep((int)simulateDelay);
Console.WriteLine("Items in cart: {0} on thread {1}",
itemCount, Thread.CurrentThread.ManagedThreadId);
}
}
}
|
程式說明:
- • 類別中多了一個型別為 Object 的私有成員:locker。此物件是用來作為獨佔鎖定之用,可以是任何參考型別。
- • AddCart 函式中增加了 lock 敘述。當兩條執行緒同時爭搶同一個鎖定物件時,其中一條執行緒會被擋住,等到被鎖定的物件被先前搶到的執行緒釋放了,才能夠取得鎖定。如此便能夠確保以 lock 關鍵字包住的程式區塊在同一時間內只會有一條執行緒進入。
這次除了增加獨佔鎖定的程式敘述,還把執行緒編號也一併秀出來,方便確認。執行結果如下圖所示:
從圖中可以看出,執行緒編號 3 和 4 都已分別啟動了,但是購物車的數量會依兩條執行緒的順序各自遞增一次,並顯示正確的結果。像這種有加上保護機制來避免多執行緒爭搶共享變數而致資料錯亂的程式寫法,我們說它是「執行緒安全的」(thread-safe)。
注意:使用獨佔鎖定的技巧時應注意避免兩條執行緒互相等待對方釋放鎖定而導致鎖死(deadlock)的情形。
前景執行緒 vs. 背景執行緒
依行為來區分,執行緒可分為兩種:前景執行緒和背景執行緒。兩者的主要區別是:當所有的前景執行緒停止時,CLR 會停止所有背景執行緒(不會拋出任何異常),並結束應用程式。若只是停止背景執行緒,則不會造成應用程式結束。因此,我們通常會把那些必須執行完畢的工作交給前景執行緒,而將比較不重要的、或可隨時中斷然後接續進行的工作交給背景執行緒負責處理。
新建立的執行緒,預設皆為前景執行緒,你可以透過 Thread 物件的 IsBackground 屬性來將它改成背景執行緒。參考以下範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using System;
using System.Threading;
namespace Ex05_BackgroundThread
{
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(MyWork);
t.IsBackground = true;
t.Start();
// 若 t 是前景執行緒,此應用程式不會結束,除非手動將它關閉;
// 若 t 是背景執行緒,此應用程式會立刻結束。
}
static void MyWork()
{
while (true)
;
}
}
}
|
程式說明:
- • 此範例程式在 Main 函式中建立一條新的執行緒之後,將它設定為背景執行緒,並令它開始執行。
- • 接著 Main 就結束了,這表示前景執行緒結束了。因此就算 MyWork 函式仍在跑無窮迴圈,應用程式仍會立刻結束。若把 t 設定為前景執行緒(預設值),則Main 函式結束之後,應用程式並不會結束,除非手動將它關閉。
=======================================[C#]執行緒(Thread)使用與執行緒上操作Ui
C#執行緒的使用方式其實跟委派脫離不了關係
需要將要執行的工作交給委派
再透過執行續執行委派的方法
簡易使用執行緒(Thread)步驟:
Step 1. 使用多執行緒時先匯入System.Threading
using System.Threading;
Step 2. 欲在執行緒上的工作(需委派的方法)
void SayHello()
{
Console.WriteLine("Hello,Thread{0}", Thread.CurrentThread.ManagedThreadId);
}
{
Console.WriteLine("Hello,Thread{0}", Thread.CurrentThread.ManagedThreadId);
}
//Thread.CurrentThread.ManagedThreadId 目前執行緒的名稱
Step 3. 建立ThreadStart委派,它是用來表示在執行緒上執行的方法(已經是系統寫好的Delagte)
//public delegate void ThreadStart ()
ThreadStart ts = new ThreadStart(SayHello); //委派方法
*既然ThreadStart這個委派無參數值
可不可以自己自訂一個委派去執行呢? Ans:不可以,稍後解釋原因
Step 4. 建立Thread並將所要執行的委派當作參數
Thread tt = new Thread(ts);
*為何不可以自訂一個delagate當作Thread的參數?
Ans:因為Thread的多載清單上
只允許 ParameterizedThreadStart 與 ThreadStart
Step 5. 啟動執行緒
tt.Start();
到這邊之後,你可能會發現,你沒辦法給定參數到執行緒內部
但是這是有方法的,而且剛剛已經有提到過
這時需要使用到 ParameterizedThreadStart
這個已經定義好的委派
publicdelegatevoid ParameterizedThreadStart(Object obj)
接著我們來看看要如何去使用它
Step 1. 使用多執行緒時先匯入System.Threading
using System.Threading;
Step 2. 欲在執行緒上的工作
(需委派的方法,記得要有參數類型必須為Object)
static void SayBye(object str)
{
Console.WriteLine("{0} Bye,Thread{1}",str, Thread.CurrentThread.ManagedThreadId);
}
{
Console.WriteLine("{0} Bye,Thread{1}",str, Thread.CurrentThread.ManagedThreadId);
}
Step 3. 建立ParameterizedThreadStart委派並委派方法
//ParameterizedThreadStart與ThreadStart相同,只是多了參數傳遞機制。
ParameterizedThreadStart pts = new ParameterizedThreadStart(SayBye);
Step 4. 建立Thread並將所要執行的委派當作參數
Thread tt2 = new Thread(pts);
Step 5. 啟動執行緒並傳入參數
tt2.Start("Master");
輸出結果:
執行緒上操作UI
在C# form操作執行緒時
特別注意的是只有在UI執行續上操作(建立控制項之執行緒)
才能對form上面的component做改變
要怎麼知道哪個是才是UI執行續呢?
當然我也不會知道
以下範例測試出錯狀況
下為範例form圖
當按下按鈕後會於textbox新增所使用執行緒名稱
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading; //使用執行緒別忘了補上
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading; //使用執行緒別忘了補上
namespace Form上UI的執行緒
{
public partial class Form1 : Form
{
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ThreadStart ts = new ThreadStart(add_listbox);
Thread t = new Thread(ts);
t.Start();
}
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ThreadStart ts = new ThreadStart(add_listbox);
Thread t = new Thread(ts);
t.Start();
}
//宣告一個委派(用於在控制視窗上執行的委派)
delegate void callbyUI();
void add_listbox()
{
delegate void callbyUI();
void add_listbox()
{
textBox1.Text += "Thread" + Thread.CurrentThread.ManagedThreadId + Environment.NewLine;
}
}
}
}
}
紅字底線處出現錯誤
如前文所說
此處的執行緒非UI執行緒導致錯誤
如何才能防止這樣問題產生?
其實方法很簡單,只要判斷是否於UI執行緒上執行
如果不是則讓UI執行緒執行即可!
作法如下
將欲在執行緒執行的方法加入判斷
//宣告一個委派(用於在控制視窗上執行的委派)
delegate void callbyUI();
void add_listbox()
{
{
//public bool InvokeRequired { get; }
//取得一個值。表示是否呼叫端是在建立控制項之執行緒(UI執行緒)以外的執行緒
//因此在進行控制項的方法呼叫時,應呼叫叫用 (Invoke) 方法。
//取得一個值。表示是否呼叫端是在建立控制項之執行緒(UI執行緒)以外的執行緒
//因此在進行控制項的方法呼叫時,應呼叫叫用 (Invoke) 方法。
if (this.InvokeRequired)
{
//建立一個在控制視窗上執行的委派
callbyUI cb = new callbyUI(add_listbox);
{
//建立一個在控制視窗上執行的委派
callbyUI cb = new callbyUI(add_listbox);
//在擁有控制項基礎視窗控制代碼的執行緒上執行委派。
this.Invoke(cb);
}
else //已經在UI執行緒
{
textBox1.Text += "Thread" + Thread.CurrentThread.ManagedThreadId + Environment.NewLine;
this.Invoke(cb);
}
else //已經在UI執行緒
{
textBox1.Text += "Thread" + Thread.CurrentThread.ManagedThreadId + Environment.NewLine;
}
===================
Introduction
處理序代表一個正在執行的應用程式,而執行緒則是處理序內部,任何一段可執行的程式碼。
一個處理序可能同時存在著一個以上的執行緒,你可以將其視為處理序所執行的工作。
多執行緒通常被應用在以下幾種狀況:
- • 耗時的運算工作:例如複雜的演算法,建立新的執行緒物件,可以讓程式進行運算同時,也可以指定其他工作,避免程式呆滯。
- • 等待回應訊息:例如讀取或是下載大量的檔案,當你等待這些工作完成之前,可以讓處理器執行其他工作,一旦接收到工作
完成的回應事件時,才回來進行未完成的工作。
若是要使用執行緒相關物件,需要引入 System.Threading 命名空間。
Example
sample1
01
|
class Program {
|
02
|
static void Main(string[] args) {
|
03
|
Program oProgram = new Program();
|
04
|
oProgram.Start();
|
05
|
Console.ReadKey();
|
06
|
}
|
07
|
|
08
|
private void Start() {
|
09
|
//建立一個執行緒,並且傳入一個委派物件 ThreadStart,並且指向 PrintOddNumber 方法。
|
10
|
Thread oThreadA = new Thread(new ThreadStart(PrintOddNumber));
|
11
|
oThreadA.Name = "A Thread";
|
12
|
|
13
|
//建立一個執行緒,並且傳入一個委派物件 ThreadStart,並且指向 PrintNumber 方法。
|
14
|
Thread oThreadB = new Thread(new ThreadStart(PrintNumber));
|
15
|
oThreadB.Name = "B Thread";
|
16
|
|
17
|
//啟動執行緒物件
|
18
|
oThreadA.Start();
|
19
|
oThreadB.Start();
|
20
|
}
|
21
|
|
22
|
//印出奇數
|
23
|
private void PrintOddNumber() {
|
24
|
for (int n1 = 1; n1 < 10; n1++) {
|
25
|
if (n1 % 2 != 0) {
|
26
|
Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
|
27
|
}
|
28
|
}
|
29
|
}
|
30
|
|
31
|
//印出偶數
|
32
|
private void PrintNumber() {
|
33
|
for (int n1 = 1; n1 < 10; n1++) {
|
34
|
if (n1 % 2 == 0) {
|
35
|
Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
|
36
|
}
|
37
|
}
|
38
|
}
|
39
|
}
|
輸出結果
sample2
01
|
public class Program {
|
02
|
public static void Main(string[] args) {
|
03
|
Program oProgram = new Program();
|
04
|
oProgram.Start();
|
05
|
Console.ReadKey();
|
06
|
}
|
07
|
|
08
|
private void Start() {
|
09
|
//建立一個執行緒,並且傳入一個委派物件 ParameterizedThreadStart,'
|
10
|
//並且設定指向 PrintOddNumber 方法。
|
11
|
Thread oThreadA = new Thread(new ParameterizedThreadStart(PrintOddNumber));
|
12
|
|
13
|
//設定執行緒的 Name
|
14
|
oThreadA.Name = "A Thread";
|
15
|
|
16
|
//建立一個執行緒,並且傳入一個委派物件 PrintNumber,'
|
17
|
//並且設定指向 PrintOddNumber 方法。
|
18
|
Thread oThreadB = new Thread(new ParameterizedThreadStart(PrintNumber));
|
19
|
|
20
|
//設定執行緒的 Name
|
21
|
oThreadB.Name = "B Thread";
|
22
|
|
23
|
//啟動執行緒物件,並且傳入參數
|
24
|
oThreadA.Start(10);
|
25
|
oThreadB.Start(10);
|
26
|
}
|
27
|
|
28
|
//印出奇數
|
29
|
private void PrintOddNumber(object value) {
|
30
|
int Number = Convert.ToInt32(value);
|
31
|
for (int n1 = 1; n1 <= Number; n1++) {
|
32
|
if (n1 % 2 != 0) {
|
33
|
Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
|
34
|
}
|
35
|
}
|
36
|
}
|
37
|
|
38
|
//印出偶數
|
39
|
private void PrintNumber(object value) {
|
40
|
int Number = Convert.ToInt32(value);
|
41
|
for (int n1 = 1; n1 <= Number; n1++) {
|
42
|
if (n1 % 2 == 0) {
|
43
|
Console.WriteLine("執行緒{0},輸出奇數{1}", Thread.CurrentThread.Name, n1);
|
44
|
}
|
45
|
}
|
46
|
}
|
47
|
}
|
輸出結果
sample3
暫停執行緒使用 Sleep()
01
|
class Program {
|
02
|
static void Main(string[] args) {
|
03
|
Program oProgram = new Program();
|
04
|
oProgram.Start();
|
05
|
Console.ReadKey();
|
06
|
}
|
07
|
private void Start() {
|
08
|
//建立委派物件並指向 PrintNumber 方法
|
09
|
ThreadStart myThreadStart = new ThreadStart(PrintNumber);
|
10
|
//建立執行緒物件
|
11
|
Thread myThread = new Thread(myThreadStart);
|
12
|
myThread.Start(); //啟動執行緒
|
13
|
}
|
14
|
public void PrintNumber() {
|
15
|
for (int n1 = 0; n1 <= 10; n1++) {
|
16
|
Console.WriteLine
|
17
|
(Thread.CurrentThread.Name +
|
18
|
"迴圈次數" + n1 + " 次");
|
19
|
if (n1 == 5) {
|
20
|
Console.WriteLine(" 執行緒暫停 5 秒鐘 !!");
|
21
|
Thread.Sleep(5000); // 暫停執行緒
|
22
|
}
|
23
|
}
|
24
|
}
|
25
|
}
|
輸出結果
sample4
執行緒使用 Join()
當執行緒本身無法決定暫停多久,必須等到其他的執行緒完成,才能繼續剩下的工作,使用這個方法,
會封鎖目前的執行緒,直到引用這個方法的執行緒執行完成之後,再進行未完成的工作。
01
|
class Program {
|
02
|
//欄位
|
03
|
private Thread ThreadA;
|
04
|
private Thread ThreadB;
|
05
|
|
06
|
static void Main(string[] args) {
|
07
|
//這邊有三個 thread : 本身應用程式的主執行緒,
|
08
|
//還有另外建立的兩條執行緒 ThreadA、 ThreadB
|
09
|
|
10
|
Program oProgram = new Program();
|
11
|
oProgram.Start();
|
12
|
Console.WriteLine(" 暫停主執行緒 !!");
|
13
|
//暫停主執行緒,等待執行緒 A 工作完畢
|
14
|
oProgram.ThreadA.Join();
|
15
|
Console.WriteLine(" 執行緒工作完成 !!");
|
16
|
Console.ReadKey();
|
17
|
}
|
18
|
|
19
|
|
20
|
private void Start() {
|
21
|
//建立執行緒 A
|
22
|
ThreadA = new Thread(new ThreadStart(PrintNumber));
|
23
|
ThreadA.Name = "A Thread";
|
24
|
//建立執行緒 B
|
25
|
ThreadB = new Thread(new ThreadStart(JoinPrintNumber));
|
26
|
ThreadB.Name = "B Thread";
|
27
|
|
28
|
//啟動執行緒
|
29
|
ThreadA.Start();
|
30
|
ThreadB.Start();
|
31
|
|
32
|
}
|
33
|
private void PrintNumber() {
|
34
|
for (int n1 = 1; n1 <= 10; n1++) {
|
35
|
Console.WriteLine
|
36
|
(Thread.CurrentThread.Name +
|
37
|
" 迴圈: " + n1 + " 次");
|
38
|
Thread.Sleep(1000);
|
39
|
if (n1 == 5) {
|
40
|
Console.WriteLine("暫停執行緒 {0} ", Thread.CurrentThread.Name);
|
41
|
//暫停目前執行緒,等待 ThreadB 執行完
|
42
|
ThreadB.Join();
|
43
|
}
|
44
|
|
45
|
}
|
46
|
}
|
47
|
private void JoinPrintNumber() {
|
48
|
for (int n1 = 11; n1 <= 20; n1++) {
|
49
|
Console.WriteLine
|
50
|
(Thread.CurrentThread.Name +
|
51
|
" 迴圈開始執行迄今第 " + n1 + " 次");
|
52
|
Thread.Sleep(1000);
|
53
|
}
|
54
|
}
|
55
|
}
|
輸出結果
sample5
藉由 Lock 敘述句完成執行緒同步作業,用以控制程式上某一段資源,這時,其他的執行緒沒權限
可以存取這份資源。
01
|
class Program {
|
02
|
private int addSum;
|
03
|
|
04
|
static void Main(string[] args) {
|
05
|
Program oProgram = new Program();
|
06
|
//建立執行緒陣列
|
07
|
Thread[] threads = new Thread[3];
|
08
|
//依序設定陣列內容
|
09
|
for (int n1 = 0; n1 < 3; n1++) {
|
10
|
Thread myThread = new Thread(new ThreadStart(oProgram.DoSum));
|
11
|
threads[n1] = myThread;
|
12
|
threads[n1].Name = "執行緒" + n1;
|
13
|
}
|
14
|
//依序啟動執行緒
|
15
|
for (int n1 = 0; n1 < 3; n1++) {
|
16
|
threads[n1].Start();
|
17
|
}
|
18
|
Console.ReadKey();
|
19
|
}
|
20
|
|
21
|
void DoSum() {
|
22
|
for (int n1 = 0; n1 < 7; n1++) {
|
23
|
//當目前執行緒執行這個方法時,會鎖住資源,其他執行緒無法存取,直到該執行緒工作完成
|
24
|
lock (this) {//this 表示,目前執行緒所在的類別,也就是鎖住這個類別的資源
|
25
|
addSum += 2;
|
26
|
Thread.Sleep(1);
|
27
|
Console.WriteLine
|
28
|
(Thread.CurrentThread.Name + ",執行第 " + n1 + " 次,addSum =" + addSum);
|
29
|
}
|
30
|
}
|
31
|
}
|
32
|
}
|
輸出結果
sample5
更進一步獲得控制,Monitor 提供一些靜態方法。
- • Enter :取得指定物件的獨佔鎖定,通常我們會直接傳入 this 關鍵字,表示監控目前產生執行緒的物件。
- • Wait:多載。 釋出物件的鎖並且封鎖目前的執行緒,直到這個執行緒重新取得鎖定為止。
- • Pulse:通知等候佇列中的執行緒,鎖定物件的狀態有所變更,總是會恢復第一個被暫停的執行緒。
- • Exit:釋出指定物件的獨佔鎖定。
完整成員方法請參考 http://msdn.microsoft.com/zh-tw/library/system.threading.monitor_members%28VS.80%29.aspx
01
|
class Program {
|
02
|
private int dataSum = 0;
|
03
|
private int dataOutput = 0;
|
04
|
|
05
|
static void Main(string[] args) {
|
06
|
Program oProgram = new Program();
|
07
|
oProgram.Start();
|
08
|
Console.Write("完成");
|
09
|
Console.ReadKey();
|
10
|
}
|
11
|
|
12
|
void Start() {
|
13
|
//建立執行緒
|
14
|
Thread TA = new Thread(new ThreadStart(DataHandle));
|
15
|
Thread TB = new Thread(new ThreadStart(DataPrint));
|
16
|
TA.Name = "執行緒 A ";
|
17
|
TB.Name = "執行緒 B ";
|
18
|
//啟動執行緒
|
19
|
TB.Start();
|
20
|
TA.Start();
|
21
|
//暫停主執行緒,等待 A、B 完成工作
|
22
|
TA.Join();
|
23
|
TB.Join();
|
24
|
}
|
25
|
|
26
|
void DataHandle() {
|
27
|
//監控進入點
|
28
|
Monitor.Enter(this);
|
29
|
|
30
|
for (int n1 = 0; n1 < 10; n1++) {
|
31
|
|
32
|
//若 dataOutput 等於 10,則鎖定目前的執行緒,並將這個執行緒鎖定的資源釋放
|
33
|
if (dataOutput == 5) Monitor.Wait(this);
|
34
|
|
35
|
dataOutput++;
|
36
|
Console.WriteLine
|
37
|
(Thread.CurrentThread.Name +
|
38
|
" 正在處理第 " + dataOutput + "筆資料 !! ");
|
39
|
Thread.Sleep(100);
|
40
|
if (dataOutput == 5) {
|
41
|
//總是會恢復第一個被暫停的執行緒 TB 會被叫醒
|
42
|
Monitor.Pulse(this);
|
43
|
Console.WriteLine();
|
44
|
}
|
45
|
}
|
46
|
//結束監控
|
47
|
Monitor.Exit(this);
|
48
|
}
|
49
|
|
50
|
void DataPrint() {
|
51
|
//監控進入點
|
52
|
Monitor.Enter(this);
|
53
|
do {
|
54
|
//若 dataOutput 等於 0,則鎖定目前的執行緒,並將這個執行緒鎖定的資源釋放
|
55
|
//所以 TB 一開始就會暫停工作
|
56
|
if (dataOutput == 0) Monitor.Wait(this);
|
57
|
|
58
|
Console.Write
|
59
|
(Thread.CurrentThread.Name +
|
60
|
" 正在列印第 " + dataOutput + "筆資料 !! ");
|
61
|
Thread.Sleep(100);
|
62
|
dataOutput--;
|
63
|
dataSum++;
|
64
|
Console.WriteLine
|
65
|
("\t總處理資料筆數 {0} !!", dataSum);
|
66
|
if (dataOutput == 0) {
|
67
|
//總是會恢復第一個被暫停的執行緒 TA 會被叫醒
|
68
|
Monitor.Pulse(this);
|
69
|
Console.WriteLine();
|
70
|
}
|
71
|
} while (dataSum < 10);
|
72
|
//結束監控
|
73
|
Monitor.Exit(this);
|
74
|
}
|
75
|
}
|
輸出結果
另外:可以使用 Lock 敘述,來取代 Monitor.Entry(this) 與 Monitor.Exit(this) ,來限制程式碼與資料的存取。
sample6
msdn :
如果這個執行緒目前沒有封鎖於等候、休眠或聯結 (Join) 狀態中,就會在下一次要開始封鎖時被插斷。
01
|
class Program {
|
02
|
long fSum1 = 0;
|
03
|
long fSum2 = 2;
|
04
|
Thread Threading1;
|
05
|
Thread Threading2;
|
06
|
static void Main(string[] args) {
|
07
|
Program oProgram = new Program();
|
08
|
oProgram.Start();
|
09
|
Console.ReadKey();
|
10
|
}
|
11
|
|
12
|
private void Start() {
|
13
|
Threading1 = new Thread(new ThreadStart(FibonnacciSeries1));
|
14
|
Threading1.Name = "ThreadA";
|
15
|
|
16
|
Threading2 = new Thread(new ThreadStart(FibonnacciSeries2));
|
17
|
Threading2.Name = "ThreadB";
|
18
|
|
19
|
Threading1.Start(); //啟動第一個執行緒
|
20
|
Threading2.Start(); //啟動第二個執行緒
|
21
|
|
22
|
Threading1.Join();
|
23
|
Threading2.Join();
|
24
|
}
|
25
|
private void FibonnacciSeries1() {
|
26
|
try {
|
27
|
for (int n1 = 0; n1 < 10; n1++) {
|
28
|
Thread.Sleep(1000);
|
29
|
fSum1 += n1;
|
30
|
if (n1 > 5) {
|
31
|
//終止執行緒
|
32
|
Threading1.Interrupt();
|
33
|
}
|
34
|
Console.WriteLine(Thread.CurrentThread.Name + " : " + " 目前總合為 = " + fSum1);
|
35
|
}
|
36
|
} //捕捉例外
|
37
|
catch (ThreadInterruptedException) {
|
38
|
Console.WriteLine(Thread.CurrentThread.Name + " 終止");
|
39
|
}
|
40
|
}
|
41
|
private void FibonnacciSeries2() {
|
42
|
try {
|
43
|
for (int n1 = 0; n1 < 10; n1++) {
|
44
|
Thread.Sleep(1000);
|
45
|
fSum2 += n1;
|
46
|
Console.WriteLine(Thread.CurrentThread.Name + " : " + " 目前總合為 = " + fSum2);
|
47
|
}
|
48
|
}
|
49
|
catch (ThreadInterruptedException) {
|
50
|
Console.WriteLine(Thread.CurrentThread.Name + " 終止");
|
51
|
}
|
52
|
}
|
53
|
}
|
輸出結果
沒有留言:
張貼留言