




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
1、C# WinForm多線程開發(fā)一 Thread類庫 Windows是一個多任務(wù)的系統(tǒng),如果你使用的是windows 2000及其以上版本,你可以通過任務(wù)管理器查看當(dāng)前系統(tǒng)運行的程序和進(jìn)程。什么是進(jìn)程呢?當(dāng)一個程序開始運行時,它就是一個進(jìn)程,進(jìn)程所指包括運行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。而一個進(jìn)程又是由多個線程所組成的,線程是程序中的一個執(zhí)行流,每個線程都有自己的專有寄存器(棧指針、程序計數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運行多個不同的線程來執(zhí)行不同的任務(wù),也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各
2、自的任務(wù)。一 關(guān)于Thread的說明在.net framework class library中,所有與多線程機制應(yīng)用相關(guān)的類都是放在System.Threading命名空間中的。其中提供Thread類用于創(chuàng)建線程,ThreadPool類用于管理線程池等等,此外還提供解決了線程執(zhí)行安排,死鎖,線程間通訊等實際問題的機制。如果你想在你的應(yīng)用程序中使用多線程,就必須包含這個類。Thread類有幾個至關(guān)重要的方法,描述如下:Start():啟動線程 Sleep(int):靜態(tài)方法,暫停當(dāng)前線程指定的毫秒數(shù) Abort():通常使用該方法來終止一個線程 Suspend():該方法并不終止未完成的線程,
3、它僅僅掛起線程,以后還可恢復(fù)。 Resume():恢復(fù)被Suspend()方法掛起的線程的執(zhí)行 線程入口使程序知道該讓這個線程干什么事,在C#中,線程入口是通過ThreadStart代理(delegate)來提供的,你可以把ThreadStart理解為一個函數(shù)指針,指向線程要執(zhí)行的函數(shù),當(dāng)調(diào)用 Thread.Start()方法后,線程就開始執(zhí)行ThreadStart所代表或者說指向的函數(shù)。 ThreadState在各種情況下的可能取值如下:Aborted:線程已停止 AbortRequested:線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止 Background:線程在后臺
4、執(zhí)行,與屬性Thread.IsBackground有關(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首先可以看看最直接的方法,也是.net 1.0下支持的方
5、法。但請注意的是,此方法在.net 2.0以后就已經(jīng)是一種錯誤的方法了。csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public partial class Form1 : Form public Form1() InitializeComponent(); private void Form1_Load(object sender, EventArgs e) Thread thread = new Thread(ThreadFuntion); thread.IsBackground = true; thread.Start(); private voi
6、d ThreadFuntion() while (true) this.textBox1.Text = DateTime.Now.ToString(); Thread.Sleep(1000); 這段code 在vs2005或者2008上都拋出異常 :Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on . 這是因為.net 2.0以后加強了安全機制,不允許在winform中直接跨線程訪問控件的屬性。那么怎
7、么解決這個問題呢,下面提供幾種方案。第一種方案: 在Thread創(chuàng)建之氣,將Control.CheckForIllegalCrossThreadCalls 設(shè)為 false。 此代碼告訴編譯器:在這個類中我們不檢查跨線程的調(diào)用是否合法(如果沒有加這句話運行也沒有異常,那么說明系統(tǒng)以及默認(rèn)的采用了不檢查的方式)。然而,這種方法不可取。我們查看CheckForIllegalCrossThreadCalls 這個屬性的定義,就會發(fā)現(xiàn)它是一個static的,也就是說無論我們在項目的什么地方修改了這個值,他就會在全局起作用。而且像這種跨線程訪問是否存在異常,我們通常都會去檢查。如果項目中其他人修改了這個
8、屬性,那么我們的方案就失敗了,我們要采取另外的方案。第二種方案csharp view plain copy 在CODE上查看代碼片派生到我的代碼片namespace TestInvoker public partial class Form1 : Form public Form1() InitializeComponent(); private void button1_Click(object sender, EventArgs e) Thread thread = new Thread(new ThreadStart(StartSomeWorkFromUIThread); thread.
9、IsBackground = true; thread.Start(); /StartSomeWorkFromUIThread(); /label1.Text = "Set value through another thread!" private void StartSomeWorkFromUIThread() if (this.InvokeRequired) BeginInvoke(new EventHandler(RunsOnWorkerThread), null); else RunsOnWorkerThread(this, null); private void
10、 RunsOnWorkerThread(object sender, EventArgs e) Thread.Sleep(2000); label1.Text = System.DateTime.Now.ToString(); 通過上敘代碼,可以看到問題已經(jīng)被解決了,通過等待異步,我們就不會總是持有主線程的控制,這樣就可以在不發(fā)生跨線程調(diào)用異常的情況下完成多線程對winform多線程控件的控制了。二 ThreadPool 與 Timer本文接上文,繼續(xù)探討WinForm中的多線程問題,再次主要探討threadpool 和timer。一 、ThreadPool線程池(ThreadPool)是一種
11、相對較簡單的方法,它適應(yīng)于一些需要多個線程而又較短任務(wù)(如一些常處于阻塞狀態(tài)的線程),它的缺點是對創(chuàng)建的線程不能加以控制,也不能設(shè)置其優(yōu)先級。由于每個進(jìn)程只有一個線程池,當(dāng)然每個應(yīng)用程序域也只有一個線程池(對線),所以你將發(fā)現(xiàn) ThreadPool類的成員函數(shù)都為static!當(dāng)你首次調(diào)用ThreadPool.QueueUserWorkItem、 ThreadPool.RegisterWaitForSingleObject等,便會創(chuàng)建線程池實例。下面我就線程池當(dāng)中的兩函數(shù)作一介紹:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public static b
12、ool QueueUserWorkItem( /調(diào)用成功則返回true WaitCallback callBack,/要創(chuàng)建的線程調(diào)用的委托 object state /傳遞給委托的參數(shù) )/它的另一個重載函數(shù)類似,只是委托不帶參數(shù)而已 此函數(shù)的作用是把要創(chuàng)建的線程排隊到線程池,當(dāng)線程池的可用線程數(shù)不為零時(線程池有創(chuàng)建線程數(shù)的限制,缺身值為25),便創(chuàng)建此線程,否則就排隊到線程池等到它有可用的線程時才創(chuàng)建。csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public static RegisteredWaitHandle RegisterWaitForSi
13、ngleObject( WaitHandle waitObject,/ 要注冊的 WaitHandle WaitOrTimerCallback callBack,/ 線程調(diào)用的委托 object state,/傳遞給委托的參數(shù) int TimeOut,/超時,單位為毫秒, bool executeOnlyOnce /是否只執(zhí)行一次 ); public delegate void WaitOrTimerCallback( object state,/也即傳遞給委托的參數(shù) bool timedOut/true表示由于超時調(diào)用,反之則因為waitObject ); 此函數(shù)的作用是創(chuàng)建一個等待線程,一
14、旦調(diào)用此函數(shù)便創(chuàng)建此線程,在參數(shù)waitObject變?yōu)榻K止?fàn)顟B(tài)或所設(shè)定的時間TimeOut到了之前,它都處于 “阻塞”狀態(tài),值得注意的一點是此“阻塞”與Thread的WaitSleepJoin狀態(tài)有很大的不同:當(dāng)某Thread處于 WaitSleepJoin狀態(tài)時CPU會定期的喚醒它以輪詢更新狀態(tài)信息,然后再次進(jìn)入WaitSleepJoin狀態(tài),線程的切換可是很費資源的;而用此函數(shù)創(chuàng)建的線程則不同,在觸發(fā)它運行之前,CPU不會切換到此線程,它既不占用CPU的時間又不浪費線程切換時間,但CPU又如何知道何時運行它?實際上線程池會生成一些輔助線程用來監(jiān)視這些觸發(fā)條件,一旦達(dá)到條件便啟動相應(yīng)的線程
15、,當(dāng)然這些輔助線程本身也占用時間,但是如果你需創(chuàng)建較多的等待線程時,使用線程池的優(yōu)勢就越加明顯。更詳細(xì)內(nèi)容demo:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片namespace TestMethodInvoker public partial class Form2 : Form public Form2() InitializeComponent(); private void button1_Click(object sender, EventArgs e) /ThreadPool.RegisterWaitForSingleObject( / ev
16、, / new WaitOrTimerCallback(WaitThreadFunc), / 4, / 2000, / false/表示每次完成等待操作后都重置計時器,直到注銷等待 / ); ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadFunc), "test1"); /Thread.Sleep(10000); private delegate void MyInvokeDelegate(string name); private void Test(object o) richTextBox1.Text += s
17、tring.Format("the object is 0 n", o); public void ThreadFunc(object b) this.Invoke(new MyInvokeDelegate(Test), b); public void WaitThreadFunc(object b, bool t) richTextBox1.Text += string.Format("the object is 0,t is 1n", b, t); 一個很值得擴(kuò)展的地方時,這里的invoke 用的是代理,其實還有其他的方法,比如 action 和fu
18、nc。實例代碼如下:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片this.Invoke(new Action<string>(this.ChangeText), o.ToString(); this.Invoke(new Action(delegate() this.textBox1.Text = o.ToString();); private void DoSomething(object o) System.Func<string, int> f = new Func<string, int>(this.GetId
19、); object result = this.Ioke(f, o.ToString(); MessageBox.Show(result.ToString(); private int GetId(string name) this.textBox1.Text = name; if (name = "Y") return 999; else return 0; 二、 Timer它適用于需周期性調(diào)用的方法,它不在創(chuàng)建計時器的線程中運行,它在由系統(tǒng)自動分配的單獨線程中運行。這和Win32中的SetTimer方法類似。它的構(gòu)造為:csharp view plain copy 在C
20、ODE上查看代碼片派生到我的代碼片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,則
21、callback 調(diào)用它的方法一次。計時器的定期行為被禁用,但使用 Change 方法可以重新啟用它。如果 period 為零 (0) 或 Infinite,并且 dueTime 不為 Infinite,則 callback 調(diào)用它的方法一次。計時器的定期行為被禁用,但使用 Change 方法可以重新啟用它。 在創(chuàng)建計時器之后若想改變它的period和dueTime,我們可以通過調(diào)用Timer的Change方法來改變:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public bool Change( int dueTime, int period );/
22、 顯然所改變的兩個參數(shù)對應(yīng)于Timer中的兩參數(shù)。三 Control.Invoke下面我們就把在Windows Form軟件中使用Invoke時的多線程要注意的問題給大家做一個介紹。首先,什么樣的操作需要考慮使用多線程?總的一條就是,負(fù)責(zé)與用戶交互的線程(以下簡稱為UI線程)應(yīng)該保持順暢,當(dāng)UI線程調(diào)用的API可能引起阻塞時間超過30毫秒時(比如訪問CD-ROM等速度超慢的外設(shè)、進(jìn)行遠(yuǎn)程調(diào)用等等)就應(yīng)該考慮使用多線程。為什么是30毫秒?30毫秒的概念是人眼可以察覺到的一個遲滯,大約等同于電影里的一幀停留的時間,最長不要超過100毫秒。第二,最方便和簡單的多線程是使用線程池。通過線程池里的線程運
23、行代碼的最簡便方法則是使用異步委托調(diào)用。注意委托調(diào)用通常是同步完成的,請使用BeginInvoke方法,這樣就可以把要調(diào)用的方法排隊到線程池里等候處理,而程序的流程會立刻返回到調(diào)用方(此處是UI線程),而調(diào)用方因此不會出現(xiàn)阻塞??纯聪旅娴睦游覀兙桶l(fā)現(xiàn)要使用線程池異步執(zhí)行代碼也并非十分復(fù)雜,這里我們利用System.Windows.Forms.MethodInvoker委托進(jìn)行異步調(diào)用。注意MethodInvoker委托不接受方法參數(shù),如果需要向異步執(zhí)行的方法傳遞參數(shù),請使用其他委托,或者需要自己定義。csharp view plain copy 在CODE上查看代碼片派生到我的代碼片priv
24、ate void StartSomeWorkFromUIThread () / 我們要做的工作相對UI線程而言臺慢了,用下面的方法異步進(jìn)行處理 MethodInvoker mi = new MethodInvoker(RunsOnWorkerThread);/這是入口方法 mi.BeginInvoke(null, null); / 這樣就不會阻塞 / 緩慢的工作在此方法內(nèi)進(jìn)行處理,使用線程池里的線程 private void RunsOnWorkerThread() DoSomethingSlow(); 歸納上述方法,對UI線程而言實際上就是:1、發(fā)出調(diào)用,2、立刻返回,具體運行過程不理了,這
25、樣UI線程就不會被阻塞。這種方法很重要,下面我們會深入介紹。除了上面的方法,還有其他使用線程池的方法,當(dāng)然如果你高興也可以自己創(chuàng)建線程。第三,在Windows Form中使用多線程的,最重要的一條注意事項是,除了創(chuàng)建控件的線程以外,絕對不要在任何其他線程里面調(diào)用控件的成員(只有極個別情況例外),也就是說控件屬于創(chuàng)建它的線程,不能從其他線程里面訪問。這一條適用于所有從System.Windows.Forms.Control派生的控件(因此可以說是幾乎所有控件),包括Form控件本身也是。舉一反三,我們很容易得出這樣的結(jié)論,控件的子控件必須由創(chuàng)建控件的線程來創(chuàng)建,比如一個表單上的按鈕,比如由創(chuàng)建表
26、單的線程來創(chuàng)建,因此,一個窗口中的所有控件實際上都活在同一個線程之中。在實際編程時,大多數(shù)的軟件的做法都是讓同一線程負(fù)責(zé)全部的控件,這就是我們所說的UI線程。看下面的例子:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片/ 這是由UI線程定義的Label控件 private Label lblStatus; / 以下方法不在UI線程上執(zhí)行 private void RunsOnWorkerThread() DoSomethingSlow(); lblStatus.Text = "Finished!" / 這是錯的 我們要特別提醒大家,很多
27、人剛開始的時候都會使用以上的方法來訪問不在同一個線程里的控件(包括筆者本人),而且在1.0版.Net 框架上似乎沒有發(fā)現(xiàn)問題,但是這根本就是錯的,更糟糕的是,程序員在這里不會得到任何錯誤提示,一開始就上當(dāng)受騙,之后會莫明其妙地發(fā)現(xiàn)其他錯誤,這就是Windows Form多線程編程的痛苦所在。筆者試過花很多時間來Debug自己寫的Splash窗口突然消失的問題,結(jié)果還是失敗了:筆者在軟件的引導(dǎo)過程中,用另外一個線程里創(chuàng)建了一個Splash窗口來顯示歡迎信息,然后嘗試把主線程里引導(dǎo)的狀態(tài)直接寫入到Splash窗口上的控件中,開始還OK,可是過一會Splash窗口就莫明其妙消失了。理解了這一點,我們
28、應(yīng)該留意到,有時候即使沒有用System.Threading.Thread來顯式創(chuàng)建一個線程,我們也可能因為使用了異步委托的BeginInvoke方法來隱式創(chuàng)建了線程(從線程池里),在這種線程里也同樣不能調(diào)用UI線程所創(chuàng)建的控件的成員。第四,由于上述限制,我們可能會感到很不方便,的確,當(dāng)我們利用一個新創(chuàng)建的線程來執(zhí)行某些花時間的運算時,怎樣知道運算進(jìn)度如何并通過UI反映給用戶呢?解決方法很多!比如熟悉多線程編程的用戶很快會想到,我們采用一些低級的同步方法,工作者線程把狀態(tài)保存到一個同步對象中,讓UI線程輪詢(Polling)該對象并反饋給用戶就可以了。不過,這還是挺麻煩的,實際上不用這樣做,C
29、ontrol類(及其派生類)對象有一個Invoke方法很特別,這是少數(shù)幾個不受線程限制的成員之一。我們前面說到,絕對不要在任何其他線程里面調(diào)用非本線程創(chuàng)建的控件的成員時,也說了“只有極個別情況例外”,這個Invoke方法就是極個別情況之一-Invoke方法可以從任何線程里面調(diào)用。下面我們來講解Invoke方法。Invoke方法的參數(shù)很簡單,一個委托,一個參數(shù)表(可選),而Invoke方法的主要功能就是幫助你在UI線程(即創(chuàng)建控件的線程)上調(diào)用委托所指定的方法。Invoke方法首先檢查發(fā)出調(diào)用的線程(即當(dāng)前線程)是不是UI線程,如果是,直接執(zhí)行委托指向的方法,如果不是,它將切換到UI線程,然后執(zhí)
30、行委托指向的方法。不管當(dāng)前線程是不是UI線程,Invoke都阻塞直到委托指向的方法執(zhí)行完畢,然后切換回發(fā)出調(diào)用的線程(如果需要的話),返回。注意,使用Invoke方法時,UI線程不能處于阻塞狀態(tài)。以下MSDN里關(guān)于Invoke方法的說明:plain view plain copy 在CODE上查看代碼片派生到我的代碼片“控件上有四種方法可以安全地從任何線程進(jìn)行調(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)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 會計領(lǐng)軍考試試題及答案
- 2025年國有企業(yè)財務(wù)管理考試試題及答案
- 2025年食品安全管理師職業(yè)資格考試試卷及答案
- 2025年數(shù)據(jù)分析師職業(yè)考試試題及答案
- 2025年護(hù)理學(xué)基礎(chǔ)與臨床路徑考試試題及答案
- 2025年信息管理理論與實踐考試試卷及答案
- 2025年法律職業(yè)資格證書考試試題及答案
- 2025年網(wǎng)絡(luò)安全基礎(chǔ)知識考試試題及答案
- 遠(yuǎn)古動物微課課件
- 企業(yè)文化培訓(xùn)課件模板
- 家政合伙合同協(xié)議書
- 【綏化】2025年黑龍江綏化市“市委書記進(jìn)校園”企事業(yè)單位引才1167人筆試歷年典型考題及考點剖析附帶答案詳解
- 合肥市2025屆高三年級5月教學(xué)質(zhì)量檢測(合肥三模)歷史試題+答案
- 2025年全國防災(zāi)減災(zāi)日班會 課件
- SL631水利水電工程單元工程施工質(zhì)量驗收標(biāo)準(zhǔn)第1部分:土石方工程
- (二調(diào))武漢市2025屆高中畢業(yè)生二月調(diào)研考試 英語試卷(含標(biāo)準(zhǔn)答案)+聽力音頻
- 數(shù)學(xué)-湖北省武漢市2025屆高中畢業(yè)生二月調(diào)研考試(武漢二調(diào))試題和解析
- DL∕T 5370-2017 水電水利工程施工通 用安全技術(shù)規(guī)程
- (高清版)TDT 1075-2023 光伏發(fā)電站工程項目用地控制指標(biāo)
- 投標(biāo)貨物的包裝、運輸方案
- 礦井選煤廠工程機電設(shè)備安裝施工組織設(shè)計#內(nèi)蒙古#附示意圖
評論
0/150
提交評論