多線程就是使程式並發(同時)執行幾個操作。
.NET 架構類庫在System.Threading 中加入了多線程的能力。因此要在前面加入引用 using System.Threading
Thread 類:建立並控制線程,設定其優先順序並擷取其狀態。
Thread 類的構造方法,主要有2中:Thread thread_name=
Thread(ThreadStart):ThreadStart 委託,它表示此線程開始執行時要調用的方法。適用於無參數的方法。
Thread(ParameterizedThreadStart):ParameterizedThreadStart 委託,它表示此線程開始執行時要調用的方法。適用於有參數傳入的方法。
一個普通的 無參 線程操作如下:
[csharp] view plaincopyprint?
01.Thread td = new Thread(xunhuan);//定義一個線程,參數是一個方法,無傳回值,採用的是委託
02.
03.//前台線程,所有的線程都執行完了,應用程式才退出,預設的都是前台線程
04.//後台線程,所有的前台線程都執行完了,就退出,不管背景線程
05.td.IsBackground = true; //設定為後台線程
06.td.Start();//啟動線程
Thread td = new Thread(xunhuan);//定義一個線程,參數是一個方法,無傳回值,採用的是委託
//前台線程,所有的線程都執行完了,應用程式才退出,預設的都是前台線程
//後台線程,所有的前台線程都執行完了,就退出,不管背景線程
td.IsBackground = true; //設定為後台線程
td.Start();//啟動線程
一個有參的線程如下:
[csharp] view plaincopyprint?
01.Thread ptd = new Thread(showname);//定義個線程,傳入的帶參數的方法。
02.ptd.IsBackground = true;
03.ptd.Start("lilei");//重載Start方法,傳遞個參數
Thread ptd = new Thread(showname);//定義個線程,傳入的帶參數的方法。
ptd.IsBackground = true;
ptd.Start("lilei");//重載Start方法,傳遞個參數
有參的方法定義,參數objec類型,
[csharp] view plaincopyprint?
01.//線程調用,帶多個參數
02. static void shownames(object names)
03. {
04. List list = names as List;
05. foreach (string name in list)
06. {
07. MessageBox.Show(name);
08. }
09.
10. }
//線程調用,帶多個參數
static void shownames(object names)
{
List list = names as List;
foreach (string name in list)
{
MessageBox.Show(name);
}
}
------------------------------------------------------------------------線程的狀態-----------------------------------------------------------------------
任何時候,線程都要處於某種線程狀態中。
新線程在Unstarted狀態中開始它的生命週期。在調用Thread類的Start方法之前,會一直保持在Unstarted狀態,調用方法之後,就會進入Started狀態,並立即將程式的控制權返回調用程式(點了線程調用後,可以立即去幹別的事)。然後,調用了Start方法的線程(也就是Started線程)和程式中其他的線程並發執行。
線程的優先順序
每個線程都有個優先順序,其範圍在ThreadPriority.Lowest和ThreadPriority.Highest之間。預設情況下,每個線程的優先順序都是Normal。
Windows作業系統支援時間分區(timeslicing)的概念,它的思路是優先順序相同的線程共用一個處理器。
------------------------------------------------------------------------線程的同步和類監視器-----------------------------------------------------------------------
通常,多個執行線程要操作共用資料。如果有權訪問共用資料的線程只能讀取資料,那就不需要阻止多個線程同時訪問共用資料。然而,當多個線程共用資料,並且其中一個或多個線程要修改資料時,可能會出現無法預知的結果。如果一個線程正在更新資料,另一個線程也試圖更新,那麼資料所反映的就第二次更新操作之後的結果。
所以可通過一次只允許一個線程訪問用於操作共用資料的代碼來解決。其他想要操作資料的線程應該等待。具有排他訪問權的線程完成對資料的操作後,等待操作線程的資料可以繼續執行。這稱為互斥或線程同步。
C#提供了兩中解決技術:
1.Monitor類:主要方法(方法傳入的參數為objec對象,一般為當前調用的線程):
Monitor.Enter():擷取獨佔鎖定。
Monitor.Wait():釋放對象上的鎖並阻止當前線程,直到重新擷取該鎖。
Monitor.Pulse():通知等待隊列中的線程對象狀態的改變。
Monitor.Exit():釋放獨佔鎖定。
2.lock關鍵字:
在對象前加個lock:
程式碼範例:Monitor的用法:
[csharp] view plaincopyprint?
01.public class mt
02. {
03. private int age;
04. private int buff = 0;//buff判斷內容是否已被更新或提取,0為未更新,1為已更新
05. int Age
06. {
07. get
08. {
09. Monitor.Enter(this);//擷取此對象的獨佔鎖定
10. if (buff == 0)//若內容為空白或未更新就使此線程等待
11. {
12. MessageBox.Show("內容為空白或未更新");
13. Monitor.Wait(this);//釋放鎖並等待
14. }
15. buff--;
16. MessageBox.Show("讀取內容");
17. Monitor.Pulse(this); //通知等待隊列的線程,此對象狀態要更改
18. Monitor.Exit(this);//釋放獨佔鎖定
19. return age;
20.
21. }
22. set
23. {
24. Monitor.Enter(this);
25. if (buff == 1)
26. {
27. MessageBox.Show("內容還沒被讀取");
28. Monitor.Wait(this);
29. }
30. buff++;
31. MessageBox.Show("寫入內容"+value);
32. age = value;
33. Monitor.Pulse(this);
34. Monitor.Exit(this);
35. }
36. }
37. }
public class mt
{
private int age;
private int buff = 0;//buff判斷內容是否已被更新或提取,0為未更新,1為已更新
int Age
{
get
{
Monitor.Enter(this);//擷取此對象的獨佔鎖定
if (buff == 0)//若內容為空白或未更新就使此線程等待
{
MessageBox.Show("內容為空白或未更新");
Monitor.Wait(this);//釋放鎖並等待
}
buff--;
MessageBox.Show("讀取內容");
Monitor.Pulse(this); //通知等待隊列的線程,此對象狀態要更改
Monitor.Exit(this);//釋放獨佔鎖定
return age;
}
set
{
Monitor.Enter(this);
if (buff == 1)
{
MessageBox.Show("內容還沒被讀取");
Monitor.Wait(this);
}
buff++;
MessageBox.Show("寫入內容"+value);
age = value;
Monitor.Pulse(this);
Monitor.Exit(this);
}
}
}
lock的用法:
[csharp] view plaincopyprint?
01.//lock的用法
02. int Age2
03. {
04. get
05. {
06. lock(this) //開始階段,自擷取了獨佔鎖定
07. {
08. if (buff == 0)//若內容為空白或未更新就使此線程等待
09. {
10. MessageBox.Show("內容為空白或未更新");
11. Monitor.Wait(this);//釋放鎖並等待
12. }
13. buff--;
14. MessageBox.Show("讀取內容");
15. Monitor.Pulse(this); //通知等待隊列的線程,此對象狀態要更改
16. return age;
17.
18. }//結束階段,釋放了獨佔鎖定
19. }
20. set
21. {
22. lock (this)
23. {
24. if (buff == 1)
25. {
26. MessageBox.Show("內容還沒被讀取");
27. Monitor.Wait(this);
28. }
29. buff++;
30. MessageBox.Show("寫入內容" + value);
31. age = value;
32. Monitor.Pulse(this);
33. }
34. }
35.
36. }
//lock的用法
int Age2
{
get
{
lock(this) //開始階段,自擷取了獨佔鎖定
{
if (buff == 0)//若內容為空白或未更新就使此線程等待
{
MessageBox.Show("內容為空白或未更新");
Monitor.Wait(this);//釋放鎖並等待
}
buff--;
MessageBox.Show("讀取內容");
Monitor.Pulse(this); //通知等待隊列的線程,此對象狀態要更改
return age;
}//結束階段,釋放了獨佔鎖定
}
set
{
lock (this)
{
if (buff == 1)
{
MessageBox.Show("內容還沒被讀取");
Monitor.Wait(this);
}
buff++;
MessageBox.Show("寫入內容" + value);
age = value;
Monitor.Pulse(this);
}
}
}
-----------------------------------------------------------------lock 與 Monitor 的注意事項------------------------------------------------------------------
Monitor注意的地方:用Monitor類的Enter方法和Exit方法來管理對象的鎖時,要想釋放鎖,必須顯示調用Exit方法。調用Exit方法之前如果某個方法中引發了一個異常,並且這個異常沒有被捕捉到,方法就會終止,而且不會調用Exit方法。因此鎖沒有被釋放。為了避免這種錯誤,可以將可能引發異常的代碼放入一個try模組,並將對Exit方法的調用放在相應的finally塊上以確保釋放鎖。
使用一個Lock塊來管理同步對象上的鎖,可以避免忘記調用Monitor類的Exit方法來釋放鎖。Lock處於某種原因二終止時,C#會隱式調用Monitor類的exit方法。如此一來,即使在代碼中出現異常,也可以將鎖釋放。