C#中的幾種多線程

來源:互聯網
上載者:User

system.threading 命名空間提供一些使得能進行多線程編程的類和介面,其中線程的建立有以下三種方法:thread、threadpool、timer。下面我就他們的使用方法逐個作一簡單介紹。
1. thread
這也許是最複雜的方法,但他提供了對線程的各種靈活控制。首先你必須使用他的建構函式建立一個線程執行個體,他的參數比較簡單,只有一個threadstart 委託:
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
提供一個線程池,該線程池可用於發送工作項目、處理非同步 I/O、代表其他線程等待以及處理計時器。
線程池(threadpool)是一種相對較簡單的方法,他適應於一些需要多個線程而又較短任務(如一些常處於阻塞狀態的線程)
,他的缺點是對建立的線程不能加以控制,也不能設定其優先順序。由於每個進程只有一個線程池,當然每個應用程式域也只有一個線程池(對線),所以你將發現
threadpool類的成員函數都為static!
當你首次調用threadpool.queueuserworkitem、
threadpool.registerwaitforsingleobject等,便會建立線程池執行個體。
下面我就線程池當中的兩函數作一介紹:

public static bool queueuserworkitem( //調用成功則返回true
waitcallback callback,//要建立的線程調用的委託
object state //傳遞給委託的參數
)//他的另一個重載函數類似,只是委託不帶參數而已
此函數的作用是把要建立的線程排隊到線程池,當線程池的可用線程數不為零時(線程池有建立線程數的限制,缺身值為25),便建立此線程,否則就排隊到線程池等到他有可用的線程時才建立。
public static registeredwaithandle registerwaitforsingleobject(
waithandle waitobject,// 要註冊的 waithandle
waitortimercallback callback,// 線程調用的委託
object state,//傳遞給委託的參數
int timeout,//逾時,單位為毫秒,
bool executeonlyonce file://是/否只執行一次
);
public delegate void waitortimercallback(
object state,//也即傳遞給委託的參數
bool timedout//true表示由於逾時調用,反之則因為waitobject
);

此函數的作用是建立一個等待線程,一旦調用此函數便建立此線程,在參數waitobject變為終止狀態或所設定的時間timeout到了之前,他
都處於“阻塞”狀態,值得注意的一點是此“阻塞”和thread的waitsleepjoin狀態有非常大的不同:當某thread處於
waitsleepjoin狀態時cpu會週期性喚醒他以輪詢更新狀態資訊,然後再次進入waitsleepjoin狀態,線程的轉換可是非常費資源的;
而用此函數建立的線程則不同,在觸發他運行之前,cpu不會轉換到此線程,他既不佔用cpu的時間又不浪費線程轉換時間,但cpu又怎麼知道何時運行他?
實際上線程池會產生一些輔助線程用來監視這些觸發條件,一旦達到條件便啟動相應的線程,當然這些輔助線程本身也佔用時間,不過如果你需建立較多的等待線程
時,使用線程池的優勢就越加明顯。見下例:

static autoresetevent ev=new autoresetevent(false);
public static int main(string[] args)
{ threadpool.registerwaitforsingleobject(
ev,
new waitortimercallback(waitthreadfunc),
4,
2000,
false//表示每次完成等待操作後都重設計時器,直到登出等待
);
threadpool.queueuserworkitem (new waitcallback (threadfunc),8);
thread.sleep (10000);
return 0;
}
public static void threadfunc(object b)
{ console.writeline ("the object is {0}",b);
for(int i=0;i<2;i++)
{ thread.sleep (1000);
ev.set();
}
}
public static void waitthreadfunc(object b,bool t)
{ console.writeline ("the object is {0},t is {1}",b,t);
}

其運行結果為:
the object is 8
the object is 4,t is false
the object is 4,t is false
the object is 4,t is true
the object is 4,t is true
the object is 4,t is true
從以上結果我們能看出線程threadfunc運行了1次,而waitthreadfunc運行了5次。我們能從waitortimercallback
中的bool t參數判斷啟動此線程的原因:t為false,則表示由於waitobject,否則則是由於逾時。另外我們也能通過object
b向線程傳遞一些參數。
3. timer
提供以指定的時間間隔執行方法的機制。
 使用 TimerCallback 委託指定希望 Timer 執行的方法。 計時器委託在構造計時器時指定,並且不能更改。 此方法不在建立計時器的線程上執行,而是在系統提供的 ThreadPool 線程上執行。
建立計時器時,可以指定在第一次執行方法之前等待的時間量(截止時間)以及此後的執行期間等待的時間量(時間周期)。 可以使用 Change 方法更改這些值或禁用計時器。
只要在使用 Timer,就必須保留對它的引用。 對於任何託管對象,如果沒有對 Timer 的引用,計時器會被記憶體回收。 即使 Timer 仍處在活動狀態,也會被回收。
 當不再需要計時器時,請使用 Dispose 方法釋放計時器持有的資源。 如果希望在計時器被釋放時接收到訊號,請使用接受 WaitHandle 的 Dispose(WaitHandle) 方法重載。 計時器已被釋放後,WaitHandle 便終止。
由計時器執行的回調方法應該是可重新進入的,因為它是在 ThreadPool 線程上調用的。 在以下兩種情況中,此回調可以同時在兩個線程池線程上執行:一是計時器間隔小於執行此回調所需的時間;二是所有線程池線程都在使用,此回調被多次排隊。
System.Threading.Timer 是一個簡單的輕量計時器,它使用回調方法並由線程池線程提供服務。 不建議將其用於 Windows
表單,因為其回調不在使用介面執行緒上進行。 System.Windows.Forms.Timer 是用於 Windows 表單的更佳選擇。
要擷取基於伺服器的計時器功能,可以考慮使用 System.Timers.Timer,它可以引發事件並具有其他功能。
這和win32中的settimer方法類似。他的構造為:
public timer(
timercallback callback,//所需調用的方法
object state,//傳遞給callback的參數
int duetime,//多久後開始調用callback
int period//調用此方法的時間間隔
);
// 如果 duetime 為0,則 callback 即時執行他的首次調用。如果 duetime 為 infinite,則 callback
不調用他的方法。計時器被禁用,但使用 change 方法能重新啟用他。如果 period 為0或 infinite,並且 duetime 不為
infinite,則 callback 調用他的方法一次。計時器的定期行為被禁用,但使用 change 方法能重新啟用他。如果 period
為零 (0) 或 infinite,並且 duetime 不為 infinite,則 callback
調用他的方法一次。計時器的定期行為被禁用,但使用 change 方法能重新啟用他。
在建立計時器之後若想改動他的period和duetime,我們能通過調用timer的change方法來改動:
[c#]
public bool change(
int duetime,
int period
);//顯然所改動的兩個參數對應於timer中的兩參數
見下例:

public static int main(string[] args)

{ console.writeline ("period is 1000");
timer tm=new timer (new timercallback (timercall),3,1000,1000);
thread.sleep (2000);
console.writeline ("period is 500");
tm.change (0,800);
thread.sleep (3000);
return 0;
}

public static void timercall(object b)
{
console.writeline ("timercallback; b is {0}",b);
}

其運行結果為:
period is 1000
timercallback;b is 3
timercallback;b is 3
period is 500
timercallback;b is 3
timercallback;b is 3
timercallback;b is 3
timercallback;b is 3
總結
從以上的簡單介紹,我們能看出他們各自使用的場合:thread適用於那些需對線程進行複雜控制的場合;threadpool適應於一些需要多個線程而又
較短任務(如一些常處於阻塞狀態的線程);timer則適用於那些需周期性調用的方法。只要我們瞭解了他們的使用特點,我們就能非常好的選擇合適的方法。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.