C#-Win多線程開(kāi)發(fā).doc_第1頁(yè)
C#-Win多線程開(kāi)發(fā).doc_第2頁(yè)
C#-Win多線程開(kāi)發(fā).doc_第3頁(yè)
C#-Win多線程開(kāi)發(fā).doc_第4頁(yè)
C#-Win多線程開(kāi)發(fā).doc_第5頁(yè)
已閱讀5頁(yè),還剩4頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

C# WinForm多線程開(kāi)發(fā)一 Thread類庫(kù) Windows是一個(gè)多任務(wù)的系統(tǒng),如果你使用的是windows 2000及其以上版本,你可以通過(guò)任務(wù)管理器查看當(dāng)前系統(tǒng)運(yùn)行的程序和進(jìn)程。什么是進(jìn)程呢?當(dāng)一個(gè)程序開(kāi)始運(yùn)行時(shí),它就是一個(gè)進(jìn)程,進(jìn)程所指包括運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。而一個(gè)進(jìn)程又是由多個(gè)線程所組成的,線程是程序中的一個(gè)執(zhí)行流,每個(gè)線程都有自己的專有寄存器(棧指針、程序計(jì)數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。多線程是指程序中包含多個(gè)執(zhí)行流,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來(lái)執(zhí)行不同的任務(wù),也就是說(shuō)允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來(lái)完成各自的任務(wù)。一 關(guān)于Thread的說(shuō)明在.net framework class library中,所有與多線程機(jī)制應(yīng)用相關(guān)的類都是放在System.Threading命名空間中的。其中提供Thread類用于創(chuàng)建線程,ThreadPool類用于管理線程池等等,此外還提供解決了線程執(zhí)行安排,死鎖,線程間通訊等實(shí)際問(wèn)題的機(jī)制。如果你想在你的應(yīng)用程序中使用多線程,就必須包含這個(gè)類。Thread類有幾個(gè)至關(guān)重要的方法,描述如下:Start():?jiǎn)?dòng)線程 Sleep(int):靜態(tài)方法,暫停當(dāng)前線程指定的毫秒數(shù) Abort():通常使用該方法來(lái)終止一個(gè)線程 Suspend():該方法并不終止未完成的線程,它僅僅掛起線程,以后還可恢復(fù)。 Resume():恢復(fù)被Suspend()方法掛起的線程的執(zhí)行 線程入口使程序知道該讓這個(gè)線程干什么事,在C#中,線程入口是通過(guò)ThreadStart代理(delegate)來(lái)提供的,你可以把ThreadStart理解為一個(gè)函數(shù)指針,指向線程要執(zhí)行的函數(shù),當(dāng)調(diào)用 Thread.Start()方法后,線程就開(kāi)始執(zhí)行ThreadStart所代表或者說(shuō)指向的函數(shù)。 ThreadState在各種情況下的可能取值如下:Aborted:線程已停止 AbortRequested:線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止 Background:線程在后臺(tái)執(zhí)行,與屬性Thread.IsBackground有關(guān) Running:線程正在正常運(yùn)行 Stopped:線程已經(jīng)被停止 StopRequested:線程正在被要求停止 Suspended:線程已經(jīng)被掛起(此狀態(tài)下,可以通過(guò)調(diào)用Resume()方法重新運(yùn)行) SuspendRequested:線程正在要求被掛起,但是未來(lái)得及響應(yīng) Unstarted:未調(diào)用Thread.Start()開(kāi)始線程的運(yùn)行 WaitSleepJoin:線程因?yàn)檎{(diào)用了Wait(),Sleep()或Join()等方法處于封鎖狀態(tài) 二 Winform中使用的thread首先可以看看最直接的方法,也是.net 1.0下支持的方法。但請(qǐng)注意的是,此方法在.net 2.0以后就已經(jīng)是一種錯(cuò)誤的方法了。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 void 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 . 這是因?yàn)?net 2.0以后加強(qiáng)了安全機(jī)制,不允許在winform中直接跨線程訪問(wèn)控件的屬性。那么怎么解決這個(gè)問(wèn)題呢,下面提供幾種方案。第一種方案: 在Thread創(chuàng)建之氣,將Control.CheckForIllegalCrossThreadCalls 設(shè)為 false。 此代碼告訴編譯器:在這個(gè)類中我們不檢查跨線程的調(diào)用是否合法(如果沒(méi)有加這句話運(yùn)行也沒(méi)有異常,那么說(shuō)明系統(tǒng)以及默認(rèn)的采用了不檢查的方式)。然而,這種方法不可取。我們查看CheckForIllegalCrossThreadCalls 這個(gè)屬性的定義,就會(huì)發(fā)現(xiàn)它是一個(gè)static的,也就是說(shuō)無(wú)論我們?cè)陧?xiàng)目的什么地方修改了這個(gè)值,他就會(huì)在全局起作用。而且像這種跨線程訪問(wèn)是否存在異常,我們通常都會(huì)去檢查。如果項(xiàng)目中其他人修改了這個(gè)屬性,那么我們的方案就失敗了,我們要采取另外的方案。第二種方案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.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 RunsOnWorkerThread(object sender, EventArgs e) Thread.Sleep(2000); label1.Text = System.DateTime.Now.ToString(); 通過(guò)上敘代碼,可以看到問(wèn)題已經(jīng)被解決了,通過(guò)等待異步,我們就不會(huì)總是持有主線程的控制,這樣就可以在不發(fā)生跨線程調(diào)用異常的情況下完成多線程對(duì)winform多線程控件的控制了。二 ThreadPool 與 Timer本文接上文,繼續(xù)探討WinForm中的多線程問(wèn)題,再次主要探討threadpool 和timer。一 、ThreadPool線程池(ThreadPool)是一種相對(duì)較簡(jiǎn)單的方法,它適應(yīng)于一些需要多個(gè)線程而又較短任務(wù)(如一些常處于阻塞狀態(tài)的線程),它的缺點(diǎn)是對(duì)創(chuàng)建的線程不能加以控制,也不能設(shè)置其優(yōu)先級(jí)。由于每個(gè)進(jìn)程只有一個(gè)線程池,當(dāng)然每個(gè)應(yīng)用程序域也只有一個(gè)線程池(對(duì)線),所以你將發(fā)現(xiàn) ThreadPool類的成員函數(shù)都為static!當(dāng)你首次調(diào)用ThreadPool.QueueUserWorkItem、 ThreadPool.RegisterWaitForSingleObject等,便會(huì)創(chuàng)建線程池實(shí)例。下面我就線程池當(dāng)中的兩函數(shù)作一介紹:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public static bool QueueUserWorkItem( /調(diào)用成功則返回true WaitCallback callBack,/要?jiǎng)?chuàng)建的線程調(diào)用的委托 object state /傳遞給委托的參數(shù) )/它的另一個(gè)重載函數(shù)類似,只是委托不帶參數(shù)而已 此函數(shù)的作用是把要?jiǎng)?chuàng)建的線程排隊(duì)到線程池,當(dāng)線程池的可用線程數(shù)不為零時(shí)(線程池有創(chuàng)建線程數(shù)的限制,缺身值為25),便創(chuàng)建此線程,否則就排隊(duì)到線程池等到它有可用的線程時(shí)才創(chuàng)建。csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public static RegisteredWaitHandle RegisterWaitForSingleObject( WaitHandle waitObject,/ 要注冊(cè)的 WaitHandle WaitOrTimerCallback callBack,/ 線程調(diào)用的委托 object state,/傳遞給委托的參數(shù) int TimeOut,/超時(shí),單位為毫秒, bool executeOnlyOnce /是否只執(zhí)行一次 ); public delegate void WaitOrTimerCallback( object state,/也即傳遞給委托的參數(shù) bool timedOut/true表示由于超時(shí)調(diào)用,反之則因?yàn)閣aitObject ); 此函數(shù)的作用是創(chuàng)建一個(gè)等待線程,一旦調(diào)用此函數(shù)便創(chuàng)建此線程,在參數(shù)waitObject變?yōu)榻K止?fàn)顟B(tài)或所設(shè)定的時(shí)間TimeOut到了之前,它都處于 “阻塞”狀態(tài),值得注意的一點(diǎn)是此“阻塞”與Thread的WaitSleepJoin狀態(tài)有很大的不同:當(dāng)某Thread處于 WaitSleepJoin狀態(tài)時(shí)CPU會(huì)定期的喚醒它以輪詢更新?tīng)顟B(tài)信息,然后再次進(jìn)入WaitSleepJoin狀態(tài),線程的切換可是很費(fèi)資源的;而用此函數(shù)創(chuàng)建的線程則不同,在觸發(fā)它運(yùn)行之前,CPU不會(huì)切換到此線程,它既不占用CPU的時(shí)間又不浪費(fèi)線程切換時(shí)間,但CPU又如何知道何時(shí)運(yùn)行它?實(shí)際上線程池會(huì)生成一些輔助線程用來(lái)監(jiān)視這些觸發(fā)條件,一旦達(dá)到條件便啟動(dòng)相應(yīng)的線程,當(dāng)然這些輔助線程本身也占用時(shí)間,但是如果你需創(chuàng)建較多的等待線程時(shí),使用線程池的優(yōu)勢(shì)就越加明顯。更詳細(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, / new WaitOrTimerCallback(WaitThreadFunc), / 4, / 2000, / false/表示每次完成等待操作后都重置計(jì)時(shí)器,直到注銷(xiāo)等待 / ); ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadFunc), test1); /Thread.Sleep(10000); private delegate void MyInvokeDelegate(string name); private void Test(object o) richTextBox1.Text += string.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); 一個(gè)很值得擴(kuò)展的地方時(shí),這里的invoke 用的是代理,其實(shí)還有其他的方法,比如 action 和func。實(shí)例代碼如下:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片this.Invoke(new Action(this.ChangeText), o.ToString(); this.Invoke(new Action(delegate() this.textBox1.Text = o.ToString();); private void DoSomething(object o) System.Func f = new Func(this.GetId); 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)建計(jì)時(shí)器的線程中運(yùn)行,它在由系統(tǒng)自動(dòng)分配的單獨(dú)線程中運(yùn)行。這和Win32中的SetTimer方法類似。它的構(gòu)造為:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public Timer( TimerCallback callback,/所需調(diào)用的方法 object state,/傳遞給callback的參數(shù) int dueTime,/多久后開(kāi)始調(diào)用callback int period/調(diào)用此方法的時(shí)間間隔 );/ 如果 dueTime 為0,則 callback 立即執(zhí)行它的首次調(diào)用。如果 dueTime 為 Infinite,則 callback 不調(diào)用它的方法。計(jì)時(shí)器被禁用,但使用 Change 方法可以重新啟用它。如果 period 為0或 Infinite,并且 dueTime 不為 Infinite,則 callback 調(diào)用它的方法一次。計(jì)時(shí)器的定期行為被禁用,但使用 Change 方法可以重新啟用它。如果 period 為零 (0) 或 Infinite,并且 dueTime 不為 Infinite,則 callback 調(diào)用它的方法一次。計(jì)時(shí)器的定期行為被禁用,但使用 Change 方法可以重新啟用它。 在創(chuàng)建計(jì)時(shí)器之后若想改變它的period和dueTime,我們可以通過(guò)調(diào)用Timer的Change方法來(lái)改變:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片public bool Change( int dueTime, int period );/ 顯然所改變的兩個(gè)參數(shù)對(duì)應(yīng)于Timer中的兩參數(shù)。三 Control.Invoke下面我們就把在Windows Form軟件中使用Invoke時(shí)的多線程要注意的問(wèn)題給大家做一個(gè)介紹。首先,什么樣的操作需要考慮使用多線程?總的一條就是,負(fù)責(zé)與用戶交互的線程(以下簡(jiǎn)稱為UI線程)應(yīng)該保持順暢,當(dāng)UI線程調(diào)用的API可能引起阻塞時(shí)間超過(guò)30毫秒時(shí)(比如訪問(wèn)CD-ROM等速度超慢的外設(shè)、進(jìn)行遠(yuǎn)程調(diào)用等等)就應(yīng)該考慮使用多線程。為什么是30毫秒?30毫秒的概念是人眼可以察覺(jué)到的一個(gè)遲滯,大約等同于電影里的一幀停留的時(shí)間,最長(zhǎng)不要超過(guò)100毫秒。第二,最方便和簡(jiǎn)單的多線程是使用線程池。通過(guò)線程池里的線程運(yùn)行代碼的最簡(jiǎn)便方法則是使用異步委托調(diào)用。注意委托調(diào)用通常是同步完成的,請(qǐng)使用BeginInvoke方法,這樣就可以把要調(diào)用的方法排隊(duì)到線程池里等候處理,而程序的流程會(huì)立刻返回到調(diào)用方(此處是UI線程),而調(diào)用方因此不會(huì)出現(xiàn)阻塞??纯聪旅娴睦游覀兙桶l(fā)現(xiàn)要使用線程池異步執(zhí)行代碼也并非十分復(fù)雜,這里我們利用System.Windows.Forms.MethodInvoker委托進(jìn)行異步調(diào)用。注意MethodInvoker委托不接受方法參數(shù),如果需要向異步執(zhí)行的方法傳遞參數(shù),請(qǐng)使用其他委托,或者需要自己定義。csharp view plain copy 在CODE上查看代碼片派生到我的代碼片private void StartSomeWorkFromUIThread () / 我們要做的工作相對(duì)UI線程而言臺(tái)慢了,用下面的方法異步進(jìn)行處理 MethodInvoker mi = new MethodInvoker(RunsOnWorkerThread);/這是入口方法 mi.BeginInvoke(null, null); / 這樣就不會(huì)阻塞 / 緩慢的工作在此方法內(nèi)進(jìn)行處理,使用線程池里的線程 private void RunsOnWorkerThread() DoSomethingSlow(); 歸納上述方法,對(duì)UI線程而言實(shí)際上就是:1、發(fā)出調(diào)用,2、立刻返回,具體運(yùn)行過(guò)程不理了,這樣UI線程就不會(huì)被阻塞。這種方法很重要,下面我們會(huì)深入介紹。除了上面的方法,還有其他使用線程池的方法,當(dāng)然如果你高興也可以自己創(chuàng)建線程。第三,在Windows Form中使用多線程的,最重要的一條注意事項(xiàng)是,除了創(chuàng)建控件的線程以外,絕對(duì)不要在任何其他線程里面調(diào)用控件的成員(只有極個(gè)別情況例外),也就是說(shuō)控件屬于創(chuàng)建它的線程,不能從其他線程里面訪問(wèn)。這一條適用于所有從System.Windows.Forms.Control派生的控件(因此可以說(shuō)是幾乎所有控件),包括Form控件本身也是。舉一反三,我們很容易得出這樣的結(jié)論,控件的子控件必須由創(chuàng)建控件的線程來(lái)創(chuàng)建,比如一個(gè)表單上的按鈕,比如由創(chuàng)建表單的線程來(lái)創(chuàng)建,因此,一個(gè)窗口中的所有控件實(shí)際上都活在同一個(gè)線程之中。在實(shí)際編程時(shí),大多數(shù)的軟件的做法都是讓同一線程負(fù)責(zé)全部的控件,這就是我們所說(shuō)的UI線程。看下面的例子:csharp view plain copy 在CODE上查看代碼片派生到我的代碼片/ 這是由UI線程定義的Label控件 private Label lblStatus; / 以下方法不在UI線程上執(zhí)行 private void RunsOnWorkerThread() DoSomethingSlow(); lblStatus.Text = Finished!; / 這是錯(cuò)的 我們要特別提醒大家,很多人剛開(kāi)始的時(shí)候都會(huì)使用以上的方法來(lái)訪問(wèn)不在同一個(gè)線程里的控件(包括筆者本人),而且在1.0版.Net 框架上似乎沒(méi)有發(fā)現(xiàn)問(wèn)題,但是這根本就是錯(cuò)的,更糟糕的是,程序員在這里不會(huì)得到任何錯(cuò)誤提示,一開(kāi)始就上當(dāng)受騙,之后會(huì)莫明其妙地發(fā)現(xiàn)其他錯(cuò)誤,這就是Windows Form多線程編程的痛苦所在。筆者試過(guò)花很多時(shí)間來(lái)Debug自己寫(xiě)的Splash窗口突然消失的問(wèn)題,結(jié)果還是失敗了:筆者在軟件的引導(dǎo)過(guò)程中,用另外一個(gè)線程里創(chuàng)建了一個(gè)Splash窗口來(lái)顯示歡迎信息,然后嘗試把主線程里引導(dǎo)的狀態(tài)直接寫(xiě)入到Splash窗口上的控件中,開(kāi)始還OK,可是過(guò)一會(huì)Splash窗口就莫明其妙消失了。理解了這一點(diǎn),我們應(yīng)該留意到,有時(shí)候即使沒(méi)有用System.Threading.Thread來(lái)顯式創(chuàng)建一個(gè)線程,我們也可能因?yàn)槭褂昧水惒轿械腂eginInvoke方法來(lái)隱式創(chuàng)建了線程(從線程池里),在這種線程里也同樣不能調(diào)用UI線程所創(chuàng)建的控件的成員。第四,由于上述限制,我們可能會(huì)感到很不方便,的確,當(dāng)我們利用一個(gè)新創(chuàng)建的線程來(lái)執(zhí)行某些花時(shí)間的運(yùn)算時(shí),怎樣知道運(yùn)算進(jìn)度如何并通過(guò)UI反映給用戶呢?解決方法很多!比如熟悉多線程編程的用戶很快會(huì)想到,我們采用一些低級(jí)的同步方法,工作者線程把狀態(tài)保存到一個(gè)同步對(duì)象中,讓UI線程輪詢(Polling)該對(duì)象并反饋給用戶就可以了。不過(guò),這還是挺麻煩的,實(shí)際上不用這樣做,Control類(及其派生類)對(duì)象有一個(gè)Invoke方法很特別,這是少數(shù)幾個(gè)不受線程限制的成員之一。我們前面說(shuō)到,絕對(duì)不要在任何其他線程里面調(diào)用非本線程創(chuàng)建的控件的成員時(shí),也說(shuō)了“只有極個(gè)別情況例外”,這個(gè)Invoke方法就是極個(gè)別情況之一-Invoke方法可以從任何線程里面調(diào)用。下面我們來(lái)講解Invoke方法。Invoke方法的參數(shù)很簡(jiǎn)單,一個(gè)委托,一個(gè)參數(shù)表(可選),而Invoke方法的主要功能就是幫助你在UI線程(即創(chuàng)建控件的線程)上調(diào)用委托所指定的方法。Invoke方法首先檢查發(fā)出調(diào)用的線程(即當(dāng)前線程)是不是UI線程,如果是,直接執(zhí)行委托指向的方法,如果不是,它將切換到UI線程,然后執(zhí)行委托指向的方法。不管當(dāng)前線程是不是UI線程,Invoke都阻塞直到委托指向的方法執(zhí)行完畢,然后切換回發(fā)出調(diào)用的線程(如果需要的話),返回。注意,使用Invoke方法時(shí),UI線程不能處于阻塞狀態(tài)。以下MSDN里關(guān)于Invoke方法的說(shuō)明:plain view plain copy 在CODE上查看代碼片派生到我的代碼片“控件上有四種方法可以安全地從任何線程進(jìn)行調(diào)用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。對(duì)于所有其他方法調(diào)用,則應(yīng)使用調(diào)用 (invoke) 方法之一封送對(duì)控件的線程的調(diào)用。 委托可以是 EventHandler 的實(shí)例,在此情況下,

溫馨提示

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

評(píng)論

0/150

提交評(píng)論