多線程 進(jìn)度條 C# .net 收藏
前言 在我們應(yīng)用程序開發(fā)過(guò)程中,經(jīng)常會(huì)遇到一些問(wèn)題,需要使用多線程技術(shù)來(lái)加以解決。本文就是通過(guò)幾個(gè)示例程序給大家講解一下多線程相關(guān)的一些主要問(wèn)題。
執(zhí)行長(zhǎng)任務(wù)操作
許多種類的應(yīng)用程序都需要長(zhǎng)時(shí)間操作,比如:執(zhí)行一個(gè)打印任務(wù),請(qǐng)求一個(gè) Web Service 調(diào)用等。用戶在這種情況下一般會(huì)去轉(zhuǎn)移做其他事情來(lái)等待任務(wù)的完成,同時(shí)還希望隨時(shí)可以監(jiān)控任務(wù)的執(zhí)行進(jìn)度。
/P> 下面的代碼片斷示例了當(dāng)長(zhǎng)任務(wù)執(zhí)行時(shí)用戶界面是如何被更新的。
// 顯示進(jìn)度條 void ShowProgress( int totalStep, int currentStep ) { _Progress.Maximum = totalStep; _Progress.Value = currentStep; } // 執(zhí)行任務(wù)
void RunTask( int seconds ) { // 每 1 / 4 秒 顯示進(jìn)度一次 for( int i = 0; i < seconds * 4; i++ ) { Thread.Sleep( 250 ); // 顯示進(jìn)度條
ShowProgress( seconds * 4, i + 1 ); } } private void _btnRun_Click( object sender, System.EventArgs e )
{ RunTask( Convert.ToInt32( _txtSecond.Value ) ); } 當(dāng)我們運(yùn)行上面的程序,在整個(gè)長(zhǎng)任務(wù)的過(guò)程中,沒有出現(xiàn)任何問(wèn)題。這樣就真的沒有問(wèn)題了嗎?當(dāng)我們切換應(yīng)用程序去做其他事情后再切換回來(lái),問(wèn)題就發(fā)生了!主窗體就會(huì)出現(xiàn)如下情況: 這個(gè)問(wèn)題當(dāng)然會(huì)發(fā)生,因?yàn)槲覀儸F(xiàn)在的應(yīng)用程序是單線程的,因此,當(dāng)線程執(zhí)行長(zhǎng)任務(wù)時(shí),它同時(shí)也就不能重畫用戶界面了。 為什么在我們切換應(yīng)用程序后,問(wèn)題才發(fā)生呢?這是因?yàn)楫?dāng)你切換當(dāng)前應(yīng)用程序到后臺(tái)再切換回前臺(tái)時(shí),我們需要重畫整個(gè)用戶界面。但是應(yīng)用程序正在執(zhí)行長(zhǎng)任務(wù),根本沒有時(shí)間處理用戶界面的重畫,問(wèn)題就會(huì)發(fā)生。
如何解決問(wèn)題呢?我們需要將長(zhǎng)任務(wù)放在后臺(tái)運(yùn)行,把用戶界面線程解放出來(lái),因此我們需要另外一個(gè)線程。
線程異步操作
我們上面程序中執(zhí)行按鈕的Click 處理如下:
private void _btnRun_Click( object sender, System.EventArgs e ) { RunTask( Convert.ToInt32( _txtSecond.Value ) ); } 回想上面剛才問(wèn)題發(fā)生的原因,直到 RunTask 執(zhí)行完成后返回,Click 處理函數(shù)始終不能夠返回,這就意味著用戶界面不能處理重畫事件或其他任何事件。一個(gè)解決方法就是創(chuàng)建另外一個(gè)線程,代碼片斷如下: using System.Threading; private int _seconds;
// 執(zhí)行任務(wù)工作線程進(jìn)入點(diǎn)
void RunTaskThreadStart() { RunTask( _seconds ); } // 通過(guò)創(chuàng)建工作線程消除用戶界面線程的阻塞問(wèn)題
private void _btnRun_Click( object sender, System.EventArgs e ) { _seconds = Convert.ToInt32( _txtSecond.Value ); Thread runTaskThread = new Thread( new ThreadStart( RunTaskThreadStart ) );
runTaskThread.Start();
} 現(xiàn)在,我們不再需要等待 RunTask 執(zhí)行完成才能夠從 Click 事件返回,我們創(chuàng)建了新的工作線程并讓它開始工作、運(yùn)行。 runTaskThread.Start(); 將我們新創(chuàng)建的工作線程調(diào)度執(zhí)行并立即返回,允許我們的用戶界面線程重新獲得控制權(quán)執(zhí)行它自己的工作?,F(xiàn)在如果用戶再切換應(yīng)用程序,因?yàn)楣ぷ骶€程在自己的空間執(zhí)行長(zhǎng)任務(wù),用戶界面線程被解放出來(lái)處理包括用戶界面重畫的各種事件,我們上面遇到的問(wèn)題就解決了。 委托異步調(diào)用
在上面的代碼中,我們注意到,我們沒有給工作線程進(jìn)入點(diǎn)(RunTaskThreadStart)傳遞任何參數(shù),我們采用聲明一個(gè)窗體類的字段 _seconds 來(lái)給工作線程傳遞參數(shù)。在某種應(yīng)用場(chǎng)合不能夠給工作線程直接傳遞參數(shù)也是一件非常痛苦的事情。
如何改進(jìn)呢?我們可以使用委托來(lái)進(jìn)行異步調(diào)用。委托是支持傳遞參數(shù)的。這樣,就消除了我們剛才的問(wèn)題,使我們能夠消除額外的字段聲明和額外的工作線程函數(shù)。
如果你不熟悉委托,你可以簡(jiǎn)單的把它理解為安全的函數(shù)指針。采用了委托異步調(diào)用,代碼片斷如下:
// 執(zhí)行任務(wù)的委托聲明 delegate void RunTaskDelegate( int seconds ); // 通過(guò)創(chuàng)建委托解決傳遞參數(shù)問(wèn)題
private void _btnRun_Click( object sender, System.EventArgs e ) { RunTaskDelegate runTask = new RunTaskDelegate( RunTask ); // 委托同步調(diào)用方式
runTask( Convert.ToInt16( _txtSecond.Value ) ); } //通過(guò)創(chuàng)建委托解決傳遞參數(shù)問(wèn)題,通過(guò)委托的異步調(diào)用消除用戶界面線程的阻塞問(wèn)題
private void _btnRun_Click( object sender, System.EventArgs e ) { RunTaskDelegate runTask = new RunTaskDelegate( RunTask ); // 委托異步調(diào)用方式
runTask.BeginInvoke( Convert.ToInt16( _txtSecond.Value ), null, null ); } 多線程安全
到這里為止,我們已經(jīng)解決了長(zhǎng)任務(wù)的難題和傳遞參數(shù)的困擾。但是我們真的解決了全部問(wèn)題嗎?回答是否定的。
我們知道 Windows 編程中有一個(gè)必須遵守的原則,那就是在一個(gè)窗體創(chuàng)建線程之外的任何線程中都不允許操作窗體。
我們上面的程序就是存在這樣的問(wèn)題:工作線程是在 ShowProgress 方法中修改了用戶界面的進(jìn)度條的屬性。那為什么程序運(yùn)行沒有出現(xiàn)問(wèn)題,運(yùn)行正常呢?
沒有發(fā)生問(wèn)題是因?yàn)槭乾F(xiàn)在的Windows XP操作系統(tǒng)對(duì)這類問(wèn)題有非常健壯的解決方法,讓我們避免了問(wèn)題的發(fā)生。但是我們現(xiàn)在的程序不能保證在其他的操作系統(tǒng)能夠運(yùn)行正常!
真正的解決方法是我們能夠認(rèn)識(shí)到問(wèn)題所在,并在程序中加以避免。
如何避免多線程的窗體資源訪問(wèn)的安全問(wèn)題呢?其實(shí)非常簡(jiǎn)單,有兩種方法: 一種方法就是不管線程是否是用戶界面線程,對(duì)用戶界面資源的訪問(wèn)統(tǒng)一由委托完成;
另一種方法是在每個(gè) Windows Forms 用戶界面類中都有一個(gè) InvokeRequired 屬性,它用來(lái)標(biāo)識(shí)當(dāng)前線程是否能夠直接訪問(wèn)窗體資源。我們只需要檢查這個(gè)屬性的值,只有當(dāng)允許直接訪問(wèn)窗體資源時(shí)才直接訪問(wèn)相應(yīng)的資源,否則,就需要通過(guò)委托進(jìn)行訪問(wèn)了。
采用第一種安全的方法的代碼片斷如下:
// 顯示進(jìn)度條的委托聲明 delegate void ShowProgressDelegate( int totalStep, int currentStep ); // 顯示進(jìn)度條
void ShowProgress( int totalStep, int currentStep ) { _Progress.Maximum = totalStep; _Progress.Value = currentStep; } // 執(zhí)行任務(wù)的委托聲明
delegate void RunTaskDelegate( int seconds ); // 執(zhí)行任務(wù)
void RunTask( int seconds ) { ShowProgressDelegate showProgress = new ShowProgressDelegate( ShowProgress ); // 每 1 / 4 秒 顯示進(jìn)度一次
for( int i = 0; i < seconds * 4; i++ ) { Thread.Sleep( 250 ); // 顯示進(jìn)度條
this.Invoke( showProgress, new object[] { seconds * 4, i + 1 } ); } } 采用第二種安全的方法的代碼片斷如下: // 顯示進(jìn)度條的委托聲明 delegate void ShowProgressDelegate( int totalStep, int currentStep ); // 顯示進(jìn)度條
void ShowProgress( int totalStep, int currentStep ) { if( _Progress.InvokeRequired ) { ShowProgressDelegate showProgress = new ShowProgressDelegate( ShowProgress ); // 為了避免工作線程被阻塞,采用異步調(diào)用委托
this.BeginInvoke( showProgress, new object[] { totalStep, currentStep } ); } else { _Progress.Maximum = totalStep; _Progress.Value = currentStep; } } // 執(zhí)行任務(wù)的委托聲明
delegate void RunTaskDelegate( int seconds ); // 執(zhí)行任務(wù)
void RunTask( int seconds ) { // 每 1 / 4 秒 顯示進(jìn)度一次 for( int i = 0; i < seconds * 4; i++ ) { Thread.Sleep( 250 ); // 顯示進(jìn)度條
ShowProgress( seconds * 4, i + 1 ); } } 至此,我們用了幾個(gè)示例說(shuō)明了如何執(zhí)行長(zhǎng)任務(wù)、如何通過(guò)多線程異步處理任務(wù)進(jìn)度的顯示并解決了多線程的安全性等問(wèn)題。希望能夠給大家對(duì)理解多線程編程、委托的使用、異步調(diào)用等方面提供一些幫助,也希望能和大家進(jìn)行進(jìn)一步的溝通和交流。 本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/hanghangaidoudou/archive/2008/03/08/2158045.aspx |
|