C#WinForm多線程開發(fā)_第1頁
C#WinForm多線程開發(fā)_第2頁
C#WinForm多線程開發(fā)_第3頁
免費預(yù)覽已結(jié)束,剩余1頁可下載查看

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、C#WinForm多線程開發(fā)Thread類庫Windows是一個多任務(wù)的系統(tǒng),如果你使用的是windows2000及其以上版本,你可以通過任務(wù)管理器查看當前系統(tǒng)運行的程序和進程。什么是進程呢?當一個程序開始運行時,它就是一個進程,進程所指包括運行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。而一個進程又是由多個線程所組成的,線程是程序中的一個執(zhí)行流,每個線程都有自己的專有寄存器(棧指針、程序計數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運行多個不同的線程來執(zhí)行不同的任務(wù),也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各自的任務(wù)。

2、一關(guān)于Thread的說明在.netframeworkclasslibrary中,所有與多線程機制應(yīng)用相關(guān)的類都是放在System.Threading命名空間中的。其中提供Thread類用于創(chuàng)建線程,ThreadPool類用于管理線程池等等,此外還提供解決了線程執(zhí)行安排,死鎖,線程間通訊等實際問題的機制。如果你想在你的應(yīng)用程序中使用多線程,就必須包含這個類。Thread類有幾個至關(guān)重要的方法,描述如下:Start():啟動線程Sleep(int):靜態(tài)方法,暫停當前線程指定的毫秒數(shù)Abort():通常使用該方法來終止一個線程Suspend。:該方法并不終止未完成的線程,它僅僅掛起線程,以后還可恢

3、復。Resume。:恢復被Suspend。方法掛起的線程的執(zhí)行線程入口使程序知道該讓這個線程干什么事,在C#中,線程入口是通過ThreadStart代理(delegate)來提供的,你可以把ThreadStart理解為一個函數(shù)指針,指向線程要執(zhí)行的函數(shù),當調(diào)用Thread.Start()方法后,線程就開始執(zhí)行ThreadStart所代表或者說指向的函數(shù)。ThreadState在各種情況下的可能取值如下:Aborted:線程已停止AbortRequested:線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止Background:線程在后臺執(zhí)行,與屬性Thread.IsBackgr

4、ound有關(guān)Running:線程正在正常運行Stopped:線程已經(jīng)被停止StopRequested:線程正在被要求停止Suspended:線程已經(jīng)被掛起(此狀態(tài)下,可以通過調(diào)用Resume()方法重新運行)SuspendRequested:線程正在要求被掛起,但是未來得及響應(yīng)Unstarted:未調(diào)用Thread.Start()開始線程的運行WaitSleepJoin:線程因為調(diào)用了Wait(),Sleep()或Join()等方法處于封鎖狀態(tài)二Winform中使用的thread首先可以看看最直接的方法,也是.net1.0下支持的方法。但請注意的是,此方法在.net2.0以后就已經(jīng)是一種錯誤的

5、方法了。csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片publicpartialclassForm1:FormpublicForm1()(InitializeComponent();privatevoidForm1_Load(objectsender,EventArgse)(Threadthread=newThread(ThreadFuntion);thread.IsBackground=true;thread.Start();privatevoidThreadFuntion()(while(true)(this.textBox1.Text=DateTime.No

6、w.ToString();Thread.Sleep(1000);這段code在vs2005或者2008上都拋出異常:Cross-threadoperationnotvalid:ControltextBoxTaccessedfromathreadotherthanthethreaditwascreatedon.這是因為.net2.0以后加強了安全機制,不允許在winform中直接跨線程訪問控件的屬性。那么怎么解決這個問題呢,下面提供幾種方案。第一種方案:在Thread創(chuàng)建之氣,將Control.CheckForIllegalCrossThreadCalls設(shè)為false。此代碼告訴編譯器:在這個

7、類中我們不檢查跨線程的調(diào)用是否合法(如果沒有加這句話運行也沒有異常,那么說明系統(tǒng)以及默認的采用了不檢查的方式)。然而,這種方法不可取。我們查看CheckForIllegalCrossThreadCalls這個屬性的定義,就會發(fā)現(xiàn)它是一個static的,也就是說無論我們在項目的什么地方修改了這個值,他就會在全局起作用。而且像這種跨線程訪問是否存在異常,我們通常都會去檢查。如果項目中其他人修改了這個屬性,那么我們的方案就失敗了,我們要采取另外的方案。第二種方案csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片namespaceTestInvoker(publicparti

8、alclassForm1:Form(publicForm1()(InitializeComponent();privatevoidbutton1_Click(objectsender,EventArgse)(Threadthread=newThread(newThreadStart(StartSomeWorkFromUIThread);thread.IsBackground=true;thread.Start();/StartSomeWorkFromUIThread();/label1.Text=Setvaluethroughanotherthread!;privatevoidStartSom

9、eWorkFromUIThread()(if(this.InvokeRequired)(BeginInvoke(newEventHandler(RunsOnWorkerThread),null);else(RunsOnWorkerThread(this,null);privatevoidRunsOnWorkerThread(objectsender,EventArgse)(Thread.Sleep(2000);label1.Text=System.DateTime.Now.ToString();通過上敘代碼,可以看到問題已經(jīng)被解決了,通過等待異步,我們就不會總是持有主線程的控制,這樣就可以在不

10、發(fā)生跨線程調(diào)用異常的情況下完成多線程對winform多線程控件的控制了。ThreadPool與Timer本文接上文,繼續(xù)探討WinForm中的多線程問題,再次主要探討threadpool和timer。一、ThreadPool線程池(ThreadPool)是一種相對較簡單的方法,它適應(yīng)于一些需要多個線程而又較短任務(wù)(如一些常處于阻塞狀態(tài)的線程),它的缺點是對創(chuàng)建的線程不能加以控制,也不能設(shè)置其優(yōu)先級。由于每個進程只有一個線程池,當然每個應(yīng)用程序域也只有一個線程池(對線),所以你將發(fā)現(xiàn)ThreadPool類的成員函數(shù)都為static!當你首次調(diào)用ThreadPool.QueueUserWorkIt

11、em、ThreadPool.RegisterWaitForSingleObject等,便會創(chuàng)建線程池實例。下面我就線程池當中的兩函數(shù)作一介紹:csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片publicstaticboolQueueUserWorkItem(/調(diào)用成功則返回trueWaitCallbackcallBack,/要創(chuàng)建的線程調(diào)用的委托objectstate/傳遞給委托的參數(shù))/它的另一個重載函數(shù)類似,只是委托不帶參數(shù)而已此函數(shù)的作用是把要創(chuàng)建的線程排隊到線程池,當線程池的可用線程數(shù)不為零時(線程池有創(chuàng)建線程數(shù)的限制,缺身值為25),便創(chuàng)建此線程,否則就排

12、隊到線程池等到它有可用的線程時才創(chuàng)建。csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片publicstaticRegisteredWaitHandleRegisterWaitForSingleObject(WaitHandlewaitObject,/要注冊的WaitHandleWaitOrTimerCallbackcallBack,/線程調(diào)用的委托objectstate,/傳遞給委托的參數(shù)intTimeOut,/超時,單位為毫秒,boolexecuteOnlyOnce/是否只執(zhí)行一次);publicdelegatevoidWaitOrTimerCallback(ob

13、jectstate,/也即傳遞給委托的參數(shù)booltimedOut/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)有很大的不同:當某Thread處于WaitSleepJoin狀態(tài)時CPU會定期的喚醒它以輪詢更新狀態(tài)信息,然后再次進入WaitSleepJoin狀態(tài),線程的切換可是很費資源的;而用此函數(shù)創(chuàng)建的線程則不同,在觸發(fā)它運行之前,CPU不會切換到此線程,

14、它既不占用CPU的時間又不浪費線程切換時間,但CPU又如何知道何時運行它?實際上線程池會生成一些輔助線程用來監(jiān)視這些觸發(fā)條件,一旦達到條件便啟動相應(yīng)的線程,當然這些輔助線程本身也占用時間,但是如果你需創(chuàng)建較多的等待線程時,使用線程池的優(yōu)勢就越加明顯。更詳細內(nèi)容demo:csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片namespaceTestMethodInvoker(publicpartialclassForm2:Form(publicForm2()(InitializeComponent();privatevoidbutton1_Click(objectsend

15、er,EventArgse)(/ThreadPool.RegisterWaitForSingleObject(/ev,/newWaitOrTimerCallback(WaitThreadFunc),/4,/2000,/false/表不每次完成等待操作后都重置計時器,直到注銷等待/);ThreadPool.QueueUserWorkItem(newWaitCallback(ThreadFunc),test1);/Thread.Sleep(10000);privatedelegatevoidMyInvokeDelegate(stringname);privatevoidTest(objecto)r

16、ichTextBox1.Text+=string.Format(theobjectis0n,o);publicvoidThreadFunc(objectb)this.Invoke(newMyInvokeDelegate(Test),b);publicvoidWaitThreadFunc(objectb,boolt)richTextBox1.Text+=string.Format(theobjectis0,tis1n,b,t);一個很值得擴展的地方時,這里的invoke用的是代理,其實還有其他的方法,比如action和func。實例代碼如下:csharpviewplaincopy在CODE上查看

17、代碼片派生到我的代碼片this.Invoke(newAction(this.ChangeText),o.ToString();this.Invoke(newAction(delegate()this.textBox1.Text=o.ToString(););privatevoidDoSomething(objecto)System.Funcf=newFunc(this.GetId);objectresult=this.Ioke(f,o.ToString();MessageBox.Show(result.ToString();privateintGetId(stringname)this.tex

18、tBox1.Text=name;if(name=Y)return999;else(return0;二、Timer它適用于需周期性調(diào)用的方法,它不在創(chuàng)建計時器的線程中運行,它在由系統(tǒng)自動分配的單獨線程中運行。這和Win32中的SetTimer方法類似。它的構(gòu)造為:csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片publicTimer(TimerCallbackcallback,/所需調(diào)用的方法objectstate,/傳遞給callback的參數(shù)intdueTime,/多久后開始調(diào)用callbackintperiod/調(diào)用此方法的時間間隔);/如果dueTime為0,

19、貝Ucallback立即執(zhí)行它的首次調(diào)用。如果dueTime為Infinite,則callback不調(diào)用它的方法。計時器被禁用,但使用Change方法可以重新啟用它。如果period為0或Infinite,并且dueTime不為Infinite,貝Ucallback調(diào)用它的方法一次。計時器的定期行為被禁用,但使用Change方法可以重新啟用它。如果period為零(0)或Infinite,并且dueTime不為Infinite,貝Ucallback調(diào)用它的方法一次。計時器的定期行為被禁用,但使用Change方法可以重新啟用它。在創(chuàng)建計時器之后若想改變它的period和dueTime,我們可以通

20、過調(diào)用Timer的Change方法來改變:csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片publicboolChange(intdueTime,intperiod);/顯然所改變的兩個參數(shù)對應(yīng)于Timer中的兩參數(shù)。一 Control.Invoke下面我們就把在WindowsForm軟件中使用Invoke時的多線程要注意的問題給大家做一個介紹。首先,什么樣的操作需要考慮使用多線程?總的一條就是,負責與用戶交互的線程(以下簡稱為UI線程)應(yīng)該保持順暢,當UI線程調(diào)用的API可能引起阻塞時間超過30毫秒時(比如訪問CD-ROM等速度超慢的外設(shè)、進行遠程調(diào)用等等)就應(yīng)該

21、考慮使用多線程。為什么是30毫秒?30毫秒的概念是人眼可以察覺到的一個遲滯,大約等同于電影里的一幀停留的時間,最長不要超過100毫秒。第二,最方便和簡單的多線程是使用線程池。通過線程池里的線程運行代碼的最簡便方法則是使用異步委托調(diào)用。注意委托調(diào)用通常是同步完成的,請使用BeginInvoke方法,這樣就可以把要調(diào)用的方法排隊到線程池里等候處理,而程序的流程會立刻返回到調(diào)用方(此處是UI線程),而調(diào)用方因此不會出現(xiàn)阻塞??纯聪旅娴睦游覀兙桶l(fā)現(xiàn)要使用線程池異步執(zhí)行代碼也并非十分復雜,這里我們利用System.Windows.Forms.MethodInvoker委托進行異步調(diào)用。注意Method

22、invoker委托不接受方法參數(shù),如果需要向異步執(zhí)行的方法傳遞參數(shù),請使用其他委托,或者需要自己定義。csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片privatevoidStartSomeWorkFromUIThread()(/我們要做的工作相對UI線程而言臺慢了,用下面的方法異步進行處理MethodInvokermi=newMethodInvoker(RunsOnWorkerThread);/這是入口方法mi.BeginInvoke(null,null);/這樣就不會阻塞/緩慢的工作在此方法內(nèi)進行處理,使用線程池里的線程privatevoidRunsOnWork

23、erThread()(DoSomethingSlow();歸納上述方法,對UI線程而言實際上就是:1、發(fā)出調(diào)用,2、立刻返回,具體運行過程不理了,這樣UI線程就不會被阻塞。這種方法很重要,下面我們會深入介紹。除了上面的方法,還有其他使用線程池的方法,當然如果你高興也可以自己創(chuàng)建線程。第三,在WindowsForm中使用多線程的,最重要的一條注意事項是,除了創(chuàng)建控件的線程以外,絕對不要在任何其他線程里面調(diào)用控件的成員(只有極個別情況例外),也就是說控件屬于創(chuàng)建它的線程,不能從其他線程里面訪問。這一條適用于所有從System.Windows.Forms.Control派生的控件(因此可以說是幾乎所

24、有控件),包括Form控件本身也是。舉一反三,我們很容易得出這樣的結(jié)論,控件的子控件必須由創(chuàng)建控件的線程來創(chuàng)建,比如一個表單上的按鈕,比如由創(chuàng)建表單的線程來創(chuàng)建,因此,一個窗口中的所有控件實際上都活在同一個線程之中。在實際編程時,大多數(shù)的軟件的做法都是讓同一線程負責全部的控件,這就是我們所說的UI線程。看下面的例子:csharpviewplaincopy在CODE上查看代碼片派生到我的代碼片/這是由UI線程定義的Label控件privateLabellblStatus;/以下方法不在UI線程上執(zhí)行privatevoidRunsOnWorkerThread()(DoSomethingSlow()

25、;lblStatus.Text=Finished!;/這是錯的我們要特別提醒大家,很多人剛開始的時候都會使用以上的方法來訪問不在同一個線程里的控件(包括筆者本人),而且在1.0版.Net框架上似乎沒有發(fā)現(xiàn)問題,但是這根本就是錯的,更糟糕的是,程序員在這里不會得到任何錯誤提示,一開始就上當受騙,之后會莫明其妙地發(fā)現(xiàn)其他錯誤,這就是WindowsForm多線程編程的痛苦所在。筆者試過花很多時間來Debug自己寫的Splash窗口突然消失的問題,結(jié)果還是失敗了:筆者在軟件的引導過程中,用另外一個線程里創(chuàng)建了一個Splash窗口來顯示歡迎信息,然后嘗試把主線程里引導的狀態(tài)直接寫入到Splash窗口上的

26、控件中,開始還OK,可是過一會Splash窗口就莫明其妙消失了。理解了這一點,我們應(yīng)該留意到,有時候即使沒有用System.Threading.Thread來顯式創(chuàng)建一個線程,我們也可能因為使用了異步委托的BeginInvoke方法來隱式創(chuàng)建了線程(從線程池里),在這種線程里也同樣不能調(diào)用UI線程所創(chuàng)建的控件的成員。第四,由于上述限制,我們可能會感到很不方便,的確,當我們利用一個新創(chuàng)建的線程來執(zhí)行某些花時間的運算時,怎樣知道運算進度如何并通過UI反映給用戶呢?解決方法很多!比如熟悉多線程編程的用戶很快會想到,我們采用一些低級的同步方法,工作者線程把狀態(tài)保存到一個同步對象中,讓UI線程輪詢(Po

27、lling)該對象并反饋給用戶就可以了。不過,這還是挺麻煩的,實際上不用這樣做,Control類(及其派生類)對象有一個Invoke方法很特別,這是少數(shù)幾個不受線程限制的成員之一。我們前面說到,絕對不要在任何其他線程里面調(diào)用非本線程創(chuàng)建的控件的成員時,也說了“只有極個別情況例外”,這個Invoke方法就是極個別情況之一-Invoke方法可以從任何線程里面調(diào)用。下面我們來講解Invoke方法。Invoke方法的參數(shù)很簡單,一個委托,一個參數(shù)表(可選),而Invoke方法的主要功能就是幫助你在UI線程(即創(chuàng)建控件的線程)上調(diào)用委托所指定的方法。Invoke方法首先檢查發(fā)出調(diào)用的線程(即當前線程)是

28、不是UI線程,如果是,直接執(zhí)行委托指向的方法,如果不是,它將切換到UI線程,然后執(zhí)行委托指向的方法。不管當前線程是不是UI線程,Invoke都阻塞直到委托指向的方法執(zhí)行完畢,然后切換回發(fā)出調(diào)用的線程(如果需要的話),返回。注意,使用Invoke方法時,UI線程不能處于阻塞狀態(tài)。以下MSDN里關(guān)于Invoke方法的說明:plainviewplaincopy在CODE上查看代碼片派生到我的代碼片控件上有四種方法可以安全地從任何線程進行調(diào)用:Invoke、BeginInvoke、EndInvoke和CreateGraphics。對于所有其他方法調(diào)用,則應(yīng)使用調(diào)用(invoke)方法之一封送對控件的線程的調(diào)用。委托可以是EventHandler的實例,在此

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論