C#多线程编程介绍——使用thread、threadpool、timer
C#多線程編程介紹——使用thread、threadpool、timer
???? 在system.threading 命名空間提供一些使得能進行多線程編程的類和接口,其中線程的創(chuàng)建有以下三種方法:thread、threadpool、timer。下面我就他們的使用方法逐個作一簡單介紹。?
1. thread?
這也許是最復(fù)雜的方法,但他提供了對線程的各種靈活控制。首先你必須使用他的構(gòu)造函數(shù)創(chuàng)建一個線程實例,他的參數(shù)比較簡單,只有一個threadstart 委托:?
public thread(threadstart start);
然后調(diào)用start()啟動他,當(dāng)然你能利用他的priority屬性來設(shè)置或獲得他的運行優(yōu)先級(enum threadpriority: normal、 lowest、 highest、 belownormal、 abovenormal)。
見下例:他首先生成了兩個線程實例t1和t2,然后分別設(shè)置他們的優(yōu)先級,接著啟動兩線程(兩線程基本相同,只不過他們輸出不相同,t1為“1”,t2為“2”,根據(jù)他們各自輸出字符個數(shù)比可大致看出他們占用cpu時間之比,這也反映出了他們各自的優(yōu)先級)。
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++)
{//每運行一個循環(huán)就寫一個“1”
dosth();
console.write("1");
}
}
public static void thread2()
{
for (int i = 0; i < 1000; i++)
{//每運行一個循環(huán)就寫一個“2”
dosth();
console.write("2");
}
}
public static void dosth()
{//用來模擬復(fù)雜運算
for (int j = 0; j < 10000000; j++)
{
int a=15;
a = a*a*a*a;
}
}
以上程式運行結(jié)果為:
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
從以上結(jié)果我們能看出,t1線程所占用cpu的時間遠比t2的多,這是因為t1的優(yōu)先級比t2的高,若我們把t1和t2的優(yōu)先級都設(shè)為normal,那結(jié)果是怎么?他們所占用的cpu時間會相同嗎?是的,正如你所料,見下圖:
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
從上例我們可看出,他的構(gòu)造類似于win32的工作線程,但更加簡單,只需把線程要調(diào)用的函數(shù)作為委托,然后把委托作為參數(shù)構(gòu)造線程實例即可。當(dāng)調(diào)用start()啟動后,便會調(diào)用相應(yīng)的函數(shù),從那函數(shù)第一行開始執(zhí)行。?
接下來我們結(jié)合線程的threadstate屬性來了解線程的控制。
threadstate是個枚舉類型,他反映的是線程所處的狀態(tài)。
當(dāng)一個thread實例剛創(chuàng)建時,他的threadstate是unstarted;當(dāng)此線程被調(diào)用start()啟動之后,他的threadstate是 running;?在此線程啟動之后,如果想讓他暫停(阻塞),能調(diào)用thread.sleep() 方法,他有兩個重載方法(sleep(int )、sleep(timespan )),只不過是表示時間量的格式不同而已,當(dāng)在某線程內(nèi)調(diào)用此函數(shù)時,他表示此線程將阻塞一段時間(時間是由傳遞給 sleep 的毫秒數(shù)或timespan決定的,但若參數(shù)為0則表示掛起此線程以使其他線程能夠執(zhí)行,指定 infinite 以無限期阻塞線程),此時他的threadstate將變?yōu)閣aitsleepjoin,另外值得注意一點的是sleep()函數(shù)被定義為了static?! 這也意味著他不能和某個線程實例結(jié)合起來用,也即不存在類似于t1.sleep(10)的調(diào)用!正是如此,sleep()函數(shù)只能由需“sleep”的線程自己調(diào)用,不允許其他線程調(diào)用,正如when to sleep是個人私事不能由他人決定。不過當(dāng)某線程處于waitsleepjoin狀態(tài)而又不得不喚醒他時,可使用thread.interrupt 方法?,他將在線程上引發(fā)threadinterruptedexception,下面我們先看一個例子(注意sleep的調(diào)用方法):
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
{//從參數(shù)可看出將導(dǎo)致休眠
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);
}
運行結(jié)果為: 1st interrupt?
2nd interrupt?
(10s后)t1 is end?
從上例我們能看出thread.interrupt方法能把程式從某個阻塞(waitsleepjoin)狀態(tài)喚醒進入對應(yīng)的中斷處理程式,然后繼續(xù)往下執(zhí)行(他的threadstate也變?yōu)閞unning),此函數(shù)的使用必須注意以下幾點:?
1 .此方法不僅可喚醒由sleep導(dǎo)致的阻塞,而且對一切可導(dǎo)致線程進入waitsleepjoin狀態(tài)的方法(如wait和join)都有效。如上例所示, 使用時要把導(dǎo)致線程阻塞的方法放入try塊內(nèi), 并把相應(yīng)的中斷處理程式放入catch塊內(nèi)。?
2 .對某一線程調(diào)用interrupt, 如他正處于waitsleepjoin狀態(tài), 則進入相應(yīng)的中斷處理程式執(zhí)行, 若此時他不處于waitsleepjoin狀態(tài), 則他后來進入此狀態(tài)時, 將被即時中斷。若在中斷前調(diào)用幾次interrupt, 只有第一次調(diào)用有效, 這正是上例我用同步的原因, 這樣才能確保第二次調(diào)用interrupt在第一個中斷后調(diào)用,否則的話可能導(dǎo)致第二次調(diào)用無效(若他在第一個中斷前調(diào)用)。你能把同步去掉試試,其結(jié)果非常可能是: 1st interrupt
上例還用了另外兩個使線程進入waitsleepjoin狀態(tài)的方法:利用同步對象和thread.join方法。join方法的使用比較簡單,他表示在調(diào)用此方法的當(dāng)前線程阻塞直至另一線程(此例中是t1)終止或經(jīng)過了指定的時間為止(若他還帶了時間量參數(shù)),當(dāng)兩個條件(若有)任一出現(xiàn),他即時結(jié)束waitsleepjoin狀態(tài)進入running狀態(tài)(可根據(jù).join方法的返回值判斷為何種條件,為true,則是線程終止;false則是時間到)。?
線程的暫停還可用thread.suspend方法,當(dāng)某線程處于running狀態(tài)時對他調(diào)用suspend方法,他將進入suspendrequested狀態(tài),但他并不會被即時掛起,直到線程到達安全點之后他才能將該線程掛起,此時他將進入suspended狀態(tài)。如對一個已處于suspended的線程調(diào)用則無效,要恢復(fù)運行只需調(diào)用thread.resume即可。?
線程的銷毀,對需銷毀的線程調(diào)用abort方法,他會在此線程上引發(fā)threadabortexception。我們可把線程內(nèi)的一些代碼放入try塊內(nèi),并把相應(yīng)處理代碼放入相應(yīng)的catch塊內(nèi),當(dāng)線程正執(zhí)行try塊內(nèi)代碼時如被調(diào)用abort,他便會跳入相應(yīng)的catch塊內(nèi)執(zhí)行,執(zhí)行完catch快內(nèi)的代碼后他將終止(若catch塊內(nèi)執(zhí)行了resetabort則不同了:他將取消當(dāng)前abort請求,繼續(xù)向下執(zhí)行。所以如要確保某線程終止的最佳用join,如上例)。?
2. threadpool?
提供一個線程池,該線程池可用于發(fā)送工作項、處理異步 I/O、代表其他線程等待以及處理計時器。
線程池(threadpool)是一種相對較簡單的方法,他適應(yīng)于一些需要多個線程而又較短任務(wù)(如一些常處于阻塞狀態(tài)的線程) ,他的缺點是對創(chuàng)建的線程不能加以控制,也不能設(shè)置其優(yōu)先級。由于每個進程只有一個線程池,當(dāng)然每個應(yīng)用程式域也只有一個線程池(對線),所以你將發(fā)現(xiàn)threadpool類的成員函數(shù)都為static! 當(dāng)你首次調(diào)用threadpool.queueuserworkitem、threadpool.registerwaitforsingleobject等,便會創(chuàng)建線程池實例。
下面我就線程池當(dāng)中的兩函數(shù)作一介紹:
public static bool queueuserworkitem( //調(diào)用成功則返回truewaitcallback callback,//要創(chuàng)建的線程調(diào)用的委托
object state //傳遞給委托的參數(shù)
)//他的另一個重載函數(shù)類似,只是委托不帶參數(shù)而已
此函數(shù)的作用是把要創(chuàng)建的線程排隊到線程池,當(dāng)線程池的可用線程數(shù)不為零時(線程池有創(chuàng)建線程數(shù)的限制,缺身值為25),便創(chuàng)建此線程,否則就排隊到線程池等到他有可用的線程時才創(chuàng)建。
public static registeredwaithandle registerwaitforsingleobject(
waithandle waitobject,// 要注冊的 waithandle
waitortimercallback callback,// 線程調(diào)用的委托
object state,//傳遞給委托的參數(shù)
int timeout,//超時,單位為毫秒,
bool executeonlyonce file://是/否只執(zhí)行一次
);
public delegate void waitortimercallback(
object state,//也即傳遞給委托的參數(shù)
bool timedout//true表示由于超時調(diào)用,反之則因為waitobject
);
此函數(shù)的作用是創(chuàng)建一個等待線程,一旦調(diào)用此函數(shù)便創(chuàng)建此線程,在參數(shù)waitobject變?yōu)榻K止狀態(tài)或所設(shè)定的時間timeout到了之前,他都處于“阻塞”狀態(tài),值得注意的一點是此“阻塞”和thread的waitsleepjoin狀態(tài)有非常大的不同:當(dāng)某thread處于waitsleepjoin狀態(tài)時cpu會定期的喚醒他以輪詢更新狀態(tài)信息,然后再次進入waitsleepjoin狀態(tài),線程的轉(zhuǎn)換可是非常費資源的;而用此函數(shù)創(chuàng)建的線程則不同,在觸發(fā)他運行之前,cpu不會轉(zhuǎn)換到此線程,他既不占用cpu的時間又不浪費線程轉(zhuǎn)換時間,但cpu又怎么知道何時運行他?實際上線程池會生成一些輔助線程用來監(jiān)視這些觸發(fā)條件,一旦達到條件便啟動相應(yīng)的線程,當(dāng)然這些輔助線程本身也占用時間,不過如果你需創(chuàng)建較多的等待線程時,使用線程池的優(yōu)勢就越加明顯。見下例:
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);
}
其運行結(jié)果為:
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
從以上結(jié)果我們能看出線程threadfunc運行了1次,而waitthreadfunc運行了5次。我們能從waitortimercallback中的bool t參數(shù)判斷啟動此線程的原因:t為false,則表示由于waitobject,否則則是由于超時。另外我們也能通過object b向線程傳遞一些參數(shù)。?
3. timer?
提供以指定的時間間隔執(zhí)行方法的機制。
?使用 TimerCallback 委托指定希望 Timer 執(zhí)行的方法。 計時器委托在構(gòu)造計時器時指定,并且不能更改。 此方法不在創(chuàng)建計時器的線程上執(zhí)行,而是在系統(tǒng)提供的 ThreadPool 線程上執(zhí)行。
創(chuàng)建計時器時,可以指定在第一次執(zhí)行方法之前等待的時間量(截止時間)以及此后的執(zhí)行期間等待的時間量(時間周期)。 可以使用 Change 方法更改這些值或禁用計時器。?
只要在使用 Timer,就必須保留對它的引用。 對于任何托管對象,如果沒有對 Timer 的引用,計時器會被垃圾回收。 即使 Timer 仍處在活動狀態(tài),也會被回收。?
?當(dāng)不再需要計時器時,請使用 Dispose 方法釋放計時器持有的資源。 如果希望在計時器被釋放時接收到信號,請使用接受 WaitHandle 的 Dispose(WaitHandle) 方法重載。 計時器已被釋放后,WaitHandle 便終止。
由計時器執(zhí)行的回調(diào)方法應(yīng)該是可重入的,因為它是在 ThreadPool 線程上調(diào)用的。 在以下兩種情況中,此回調(diào)可以同時在兩個線程池線程上執(zhí)行:一是計時器間隔小于執(zhí)行此回調(diào)所需的時間;二是所有線程池線程都在使用,此回調(diào)被多次排隊。
System.Threading.Timer 是一個簡單的輕量計時器,它使用回調(diào)方法并由線程池線程提供服務(wù)。 不建議將其用于 Windows 窗體,因為其回調(diào)不在用戶界面線程上進行。 System.Windows.Forms.Timer 是用于 Windows 窗體的更佳選擇。 要獲取基于服務(wù)器的計時器功能,可以考慮使用 System.Timers.Timer,它可以引發(fā)事件并具有其他功能。
這和win32中的settimer方法類似。他的構(gòu)造為:
public timer(timercallback callback,//所需調(diào)用的方法
object state,//傳遞給callback的參數(shù)
int duetime,//多久后開始調(diào)用callback
int period//調(diào)用此方法的時間間隔
);
// 如果 duetime 為0,則 callback 即時執(zhí)行他的首次調(diào)用。如果 duetime 為 infinite,則 callback 不調(diào)用他的方法。計時器被禁用,但使用 change 方法能重新啟用他。如果 period 為0或 infinite,并且 duetime 不為 infinite,則 callback 調(diào)用他的方法一次。計時器的定期行為被禁用,但使用 change 方法能重新啟用他。如果 period 為零 (0) 或 infinite,并且 duetime 不為 infinite,則 callback 調(diào)用他的方法一次。計時器的定期行為被禁用,但使用 change 方法能重新啟用他。?
在創(chuàng)建計時器之后若想改動他的period和duetime,我們能通過調(diào)用timer的change方法來改動:?
[c#]?
public bool change(?
int duetime,?
int period?
);//顯然所改動的兩個參數(shù)對應(yīng)于timer中的兩參數(shù)?
見下例:
{ 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);
}
其運行結(jié)果為:
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?
總結(jié)?
從以上的簡單介紹,我們能看出他們各自使用的場合:thread適用于那些需對線程進行復(fù)雜控制的場合;threadpool適應(yīng)于一些需要多個線程而又較短任務(wù)(如一些常處于阻塞狀態(tài)的線程);timer則適用于那些需周期性調(diào)用的方法。只要我們了解了他們的使用特點,我們就能非常好的選擇合適的方法。
轉(zhuǎn)載于:https://www.cnblogs.com/zxtceq/p/7771269.html
總結(jié)
以上是生活随笔為你收集整理的C#多线程编程介绍——使用thread、threadpool、timer的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新的博客の家
- 下一篇: ztree实现左边动态生成树,右边为具体