程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> Visual C#中的多線程編程

Visual C#中的多線程編程

編輯:C#入門知識

C#是.Net平台的通用開發工具,它能夠建造所有的.Net應用。在.Net中所有線程都運行在應用程序域(AppDomain)中,這也許讓你想到Win32進程,實際上它們還是有很大的不同。應用程序域提供了一種安全而通用的處理單元,公共語言運行庫可使用它來隔離應用程序。注意在.Net中應用程序的隔離是應用程序域而不是進程,在單個進程中可以存在幾個應用程序域,而且線程可以跨越應用程序域的范圍,某個線程中的方法可以調用另一個線程的方法,這樣的話就不會造成進程間調用或進程間切換等方面的額外開銷。可以說應用程序域是物理進程(也即win32中的Process)內的邏輯進程。
在Visul C#中System.Threading 命名空間提供一些使得可以進行多線程編程的類和接口,其中線程的創建有以下三種方法:Thread、ThreadPool、Timer。下面我就它們的使用方法逐個作一簡單介紹。
1. Thread
這也許是最復雜的方法,但它提供了對線程的各種靈活控制。首先你必須使用它的構造函數創建一個線程實例,它的參數比較簡單,只有一個ThreadStart 委托:
[C#]
public Thread(ThreadStart start);
然後調用Start()啟動它,當然你可以利用它的Priority屬性來設置或獲得它的運行優先級(enum ThreadPriority: Normal、 Lowest、 Highest、 BelowNormal、 AboveNormal)。見下例:它首先生成了兩個線程實例t1和t2,然後分別設置它們的優先級,接著啟動兩線程(兩線程基本一樣,只不過它們輸出不一樣,t1為“1”,t2為“2”,根據它們各自輸出字符個數比可大致看出它們占用CPU時間之比,這也反映出了它們各自的優先級)。
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(Thread1));
Thread t2 = new Thread(new ThreadStart(Thread2));


t1.Priority = ThreadPriority.BelowNormal ;
t2.Priority = ThreadPriority.Lowest ;
t1.Start();
t2.Start();
}
public static void Thread1()
{
for (int i = 1; i < 1000; i++)
{//每運行一個循環就寫一個“1”
dosth();
Console.Write("1");
}
}
public static void Thread2()
{
for (int i = 0; i < 1000; i++)
{//每運行一個循環就寫一個“2”
dosth();
Console.Write("2");
}
}
public static void dosth()
{//用來模擬復雜運算
for (int j = 0; j < 10000000; j++)
{
int a=15;
a = a*a*a*a;
}
}
以上程序運行結果為:


11111111111111111111111111111111111111111121111111111111111111111111111111111111111112


11111111111111111111111111111111111111111121111111111111111111111111111111111111111112


11111111111111111111111111111111111111111121111111111111111111111111111111111111111112

從以上結果我們可以看出,t1線程所占用CPU的時間遠比t2的多,這是因為t1的優先級比t2的高,若我們把t1和t2的優先級都設為Normal,那結果是如何?它們所占用的CPU時間會一樣嗎?是的,正如你所料,見下圖:


121211221212121212121212121212121212121212121212121212121212121212121


212121212121212121212121212121212121212121212121212121212121212121212


121212121212121212



從上例我們可看出,它的構造類似於win32的工作線程,但更加簡單,只需把線程要調用的函數作為委托,然後把委托作為參數構造線程實例即可。當調用Start()啟動後,便會調用相應的函數,從那函數第一行開始執行。
接下來我們結合線程的ThreadState屬性來了解線程的控制。ThreadState是一個枚舉類型,它反映的是線程所處的狀態。當一個Thread實例剛創建時,它的ThreadState是Unstarted;當此線程被調用Start()啟動之後,它的ThreadState是 Running;  在此線程啟動之後,如果想讓它暫停(阻塞),可以調用Thread.Sleep() 方法,它有兩個重載方法(Sleep(int )、Sleep(Timespan )),只不過是表示時間量的格式不同而已,當在某線程內調用此函數時,它表示此線程將阻塞一段時間(時間是由傳遞給 Sleep 的毫秒數或Timespan決定的,但若參數為0則表示掛起此線程以使其它線程能夠執行,指定 Infinite 以無限期阻塞線程),此時它的ThreadState將變為WaitSleepJoin,另外值得注意一點的是Sleep()函數被定義為了static?! 這也意味著它不能和某個線程實例結合起來用,也即不存在類似於t1.Sleep(10)的調用!正是如此,Sleep()函數只能由需“Sleep”的線程自己調用,不允許其它線程調用,正如when to Sleep是個人私事不能由它人決定。但是當某線程處於WaitSleepJoin狀態而又不得不喚醒它時,可使用Thread.Interrupt 方法 ,它將在線程上引發ThreadInterruptedException,下面我們先看一個例子(注意Sleep的調用方法):
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(Thread1));
t1.Start();
t1.Interrupt ();
E.WaitOne ();
t1.Interrupt ();
t1.Join();
Console.WriteLine(“t1 is end”);
}
static AutoResetEvent E = new AutoResetEvent(false);
public static void Thread1()
{
try
{//從參數可看出將導致休眠
Thread.Sleep(Timeout.Infinite);
}
catch(System.Threading.ThreadInterruptedException e)
{//中斷處理程序
Console.WriteLine (" 1st interrupt");
}
E.Set ();
try
{// 休眠
Thread.Sleep(Timeout.Infinite );
}
catch(System.Threading.ThreadInterruptedException e)
{
Console.WriteLine (" 2nd interrupt");
}//暫停10秒
Thread.Sleep (10000);
}
運行結果為: 1st interrupt
2nd interrupt
(10s後)t1 is end
從上例我們可以看出Thread.Interrupt方法可以把程序從某個阻塞(WaitSleepJoin)狀態喚醒進入對應的中斷處理程序,然後繼續往下執行(它的ThreadState也變為Running),此函數的使用必須注意以下幾點:
1 .此方法不僅可喚醒由Sleep導致的阻塞,而且對一切可導致線程進入WaitSleepJoin狀態的方法(如Wait和Join)都有效。如上例所示, 使用時要把導致線程阻塞的方法放入try塊內, 並把相應的中斷處理程序放入catch塊內。
2 .對某一線程調用Interrupt, 如它正處於WaitSleepJoin狀態, 則進入相應的中斷處理程序執行, 若此時它不處於WaitSleepJoin狀態, 則它後來進入此狀態時, 將被立即中斷。若在中斷前調用幾次Interrupt, 只有第一次調用有效, 這正是上例我用同步的原因, 這樣才能確保第二次調用Interrupt在第一個中斷後調用,否則的話可能導致第二次調用無效(若它在第一個中斷前調用)。你可以把同步去掉試試,其結果很可能是: 1st interrupt
上例還用了另外兩個使線程進入WaitSleepJoin狀態的方法:利用同步對象和Thread.Join方法。Join方法的使用比較簡單,它表示在調用此方法的當前線程阻塞直至另一線程(此例中是t1)終止或者經過了指定的時間為止(若它還帶了時間量參數),當兩個條件(若有)任一出現,它立即結束WaitSleepJoin狀態進入Running狀態(可根據.Join方法的返回值判斷為何種條件,為true,則是線程終止;false則是時間到)。
線程的暫停還可用Thread.Suspend方法,當某線程處於Running狀態時對它調用Suspend方法,它將進入SuspendRequested狀態,但它並不會被立即掛起,直到線程到達安全點之後它才可以將該線程掛起,此時它將進入Suspended狀態。如對一個已處於Suspended的線程調用則無效,要恢復運行只需調用Thread.Resume即可。
最後我們談的是線程的銷毀,我們可以對需銷毀的線程調用Abort方法,它會在此線程上引發ThreadAbortException。我們可把線程內的一些代碼放入try塊內,並把相應處理代碼放入相應的catch塊內,當線程正執行try塊內代碼時如被調用Abort,它便會跳入相應的catch塊內執行,執行完catch快內的代碼後它將終止(若catch塊內執行了ResetAbort則不同了:它將取消當前Abort請求,繼續向下執行。所以如要確保某線程終止的最好用Join,如上例)。






2. ThreadPool
線程池(ThreadPool)是一種相對較簡單的方法,它適應於一些需要多個線程而又較短任務(如一些常處於阻塞狀態的線程) ,它的缺點是對創建的線程不能加以控制,也不能設置其優先級。由於每個進程只有一個線程池,當然每個應用程序域也只有一個線程池(對線),所以你將發現ThreadPool類的成員函數都為static! 當你首次調用ThreadPool.QueueUserWorkItem、ThreadPool.RegisterWaitForSingleObject等,便會創建線程池實例。下面我就線程池當中的兩函數作一介紹:
[C#]
public static bool QueueUserWorkItem( //調用成功則返回true
WaitCallback callBack,//要創建的線程調用的委托
object state //傳遞給委托的參數
)//它的另一個重載函數類似,只是委托不帶參數而已
此函數的作用是把要創建的線程排隊到線程池,當線程池的可用線程數不為零時(線程池有創建線程數的限制,缺身值為25),便創建此線程,否則就排隊到線程池等到它有可用的線程時才創建。
[C#]
public static RegisteredWaitHandle RegisterWai

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved