CSDN第一期總結之三:Thread的問題

C#是一門支持多線程的語言,因此線程的使用也是比較常見的。由於線程的知識在Win32編程的時候已經說得過多,所以在.Net中很少介紹這部分(可能.Net不覺得這部分是它所特有的)。

 

那麼線程相關的問題大致有如下四類(這篇文章只討論單線程、單線程與UI線程這兩方面的問題)。

問題一,線程的基本操作,例如:暫停、繼續、停止等;

問題二,如何向線程傳遞參數或者從中得到其返回值;

問題三,如何使線程所佔用的CPU不要老是百分之百;

最後一個,也是問題最多的,就是如何在子線程來控制UI中的控件,換句話說,就是在線程中控制窗體某些控件的顯示。

 

對於問題一,我不建議使用Thread類提供的SuspendResume以及Abort這三個方法,前兩個有問題,好像在VS05已經屏蔽這兩個方法;對於Abort來說,除了資源沒有得到及時釋放外,有時候會出現異常。如何做呢,通過設置開關變量來完成。

 

對於問題二,我不建議使用靜態成員來完成,僅僅爲了線程而破壞類的封裝有些得不償失。那如何做呢,通過創建單獨的線程類來完成。

 

對於問題三來說,造成這個原因是由於線程中進行不間斷的循環操作,從而使CPU完全被子線程佔有。那麼處理此類問題,其實很簡單,在適當的位置調用Thread.Sleep(20)來釋放所佔有CPU資源,不要小看這20毫秒的睡眠,它的作用可是巨大的,可以使其他線程得到CPU資源,從而使你的CPU使用效率降下來。

 

看完前面的三個問題的解釋,對於如何做似乎沒有給出一個明確的答案,爲了更好地說明如何解決這三個問題,我用一個比較完整的例子展現給大家,代碼如下。

//--------------------------- Sub-thread class ---------------------------------------

//------------------------------------------------------------------------------------

//---File:          clsSubThread

//---Description:   The sub-thread template class file

//---Author:        Knight

//---Date:          Aug.21, 2006

//------------------------------------------------------------------------------------

//---------------------------{Sub-thread class}---------------------------------------

namespace ThreadTemplate

{

    using System;

    using System.Threading;

    using System.IO;

    ///<summary>

    /// Summary description for clsSubThread.

    ///</summary>

    public class clsSubThread:IDisposable

    {

        private Thread thdSubThread = null;

        private Mutex mUnique = new Mutex();

 

        private bool blnIsStopped;

        private bool blnSuspended;

        private bool blnStarted;

        private int nStartNum;

 

        public bool IsStopped

        {

            get{ return blnIsStopped; }

        }

        public bool IsSuspended

        {

            get{ return blnSuspended; }

        }

        public int ReturnValue

        {

            get{ return nStartNum;}

        }

 

   

        public clsSubThread( int StartNum )

        {

            //

            // TODO: Add constructor logic here

            //

            blnIsStopped = true;

            blnSuspended = false;

            blnStarted = false;

           

            nStartNum = StartNum;

        }

 

        ///<summary>

        /// Start sub-thread

        ///</summary>

        public void Start()

        {

            if( !blnStarted )

            {

                thdSubThread = new Thread( new ThreadStart( SubThread ) );

                blnIsStopped = false;

                blnStarted = true;

                thdSubThread.Start();

            }

        }

 

        ///<summary>

        /// Thread entry function

        ///</summary>

        private void SubThread()

        {

            do

            {

                // Wait for resume-command if got suspend-command here 

                mUnique.WaitOne();

                mUnique.ReleaseMutex();

 

                nStartNum++;

           

                Thread.Sleep(1000); // Release CPU here

            }while( blnIsStopped == false );

        }

 

        ///<summary>

        /// Suspend sub-thread

        ///</summary>

        public void Suspend()

        {

            if( blnStarted && !blnSuspended )

            {

                blnSuspended = true;

                mUnique.WaitOne();

            }

        }

   

        ///<summary>

        /// Resume sub-thread

        ///</summary>

        public void Resume()

        {

            if( blnStarted && blnSuspended )

            {

                blnSuspended = false;

                mUnique.ReleaseMutex();

            }

        }

 

        ///<summary>

        /// Stop sub-thread

        ///</summary>

        public void Stop()

        {

            if( blnStarted )

            {

                if( blnSuspended )

                    Resume();

 

                blnStarted = false;

                blnIsStopped = true;

                thdSubThread.Join();

            }

        }

        #region IDisposable Members

        ///<summary>

        /// Class resources dispose here

        ///</summary>

        public void Dispose()

        {

            // TODO:  Add clsSubThread.Dispose implementation

            Stop();//Stop thread first

            GC.SuppressFinalize( this );

        }

 

        #endregion

    }

}

 

那麼對於調用呢,就非常簡單了,如下:

        // Create new sub-thread object with parameters

        clsSubThread mySubThread = new clsSubThread( 5 );

 

        mySubThread.Start();//Start thread

       

        Thread.Sleep( 2000 );

        mySubThread.Suspend();//Suspend thread

 

        Thread.Sleep( 2000 );

        mySubThread.Resume();//Resume thread

 

        Thread.Sleep( 2000 );

        mySubThread.Stop();//Stop thread

 

        //Get thread's return value

        Debug.WriteLine( mySubThread.ReturnValue );

 

        //Release sub-thread object

        mySubThread.Dispose();

 

在回過頭來看看前面所說的三個問題。

對於問題一來說,首先需要局部成員的支持,那麼

        private Mutex mUnique = new Mutex();

 

        private bool blnIsStopped;

        private bool blnSuspended;

        private bool blnStarted;

 

光看成員名稱,估計大家都已經猜出其代表的意思。接下來需要修改線程入口函數,要是這些開關變量能發揮作用,那麼看看SubThread這個函數。

        ///<summary>

        /// Thread entry function

        ///</summary>

        private void SubThread()

        {

            do

            {

                // Wait for resume-command if got suspend-command here 

                mUnique.WaitOne();

                mUnique.ReleaseMutex();

 

                nStartNum++;

           

                Thread.Sleep(1000);

            }while( blnIsStopped == false );

        }

 

函數比較簡單,不到十句,可能對於“blnIsStopped == false”這個判斷來說,大家還比較好理解,這是一個普通的判斷,如果當前Stop開關打開了,就停止循環;否則一直循環。

大家比較迷惑的可能是如下這兩句:

                mUnique.WaitOne();

                mUnique.ReleaseMutex();

這兩句的目的是爲了使線程在Suspend操作的時候能發揮效果,爲了解釋這兩句,需要結合SuspendResume這兩個方法,它倆的代碼如下。

        ///<summary>

        /// Suspend sub-thread

        ///</summary>

        public void Suspend()

        {

            if( blnStarted && !blnSuspended )

            {

                blnSuspended = true;

                mUnique.WaitOne();

            }

        }

   

        ///<summary>

        /// Resume sub-thread

        ///</summary>

        public void Resume()

        {

            if( blnStarted && blnSuspended )

            {

                blnSuspended = false;

                mUnique.ReleaseMutex();

            }

        }

 

爲了更好地說明,還需要先簡單說說Mutex類型。對於此類型對象,當調用對象的WaitOne之後,如果此時沒有其他線程對它使用的時候,就立刻獲得信號量,繼續執行代碼;當再調用ReleaseMutex之前,如果再調用對象的WaitOne方法,就會一直等待,直到獲得信號量的調用ReleaseMutex來進行釋放。這就好比衛生間的使用,如果沒有人使用則可以直接使用,否則只有等待。

明白了這一點後,再來解釋這兩句所能出現的現象。

                mUnique.WaitOne();

                mUnique.ReleaseMutex();

 

當在線程函數中,執行到“mUnique.WaitOne();”這一句的時候,如果此時外界沒有發送Suspend消息,也就是信號量沒有被佔用,那麼這一句可以立刻返回。那麼爲什麼要緊接着釋放呢,因爲不能總佔着信號量,立即釋放信號量是避免在發送Suspend命令的時候出現等待;如果此時外界已經發送了Suspend消息,也就是說信號量已經被佔用,此時“mUnique.WaitOne();”不能立刻返回,需要等到信號量被釋放才能繼續進行,也就是需要調用Resume的時候,“mUnique.WaitOne();”才能獲得信號量進行繼續執行。這樣才能達到真正意義上的SuspendResume

 

至於線程的StartStop來說,相對比較簡單,這裏我就不多說了。

 

現在再來分析一下問題二,其實例子比較明顯,是通過構造函數和屬性來完成參數和返回值,這一點我也不多說了。如果線程參數比較多的話,可以考慮屬性來完成,類似於返回值。

 

問題三,我就更不用多說了。有人說了,如果子線程中的循環不能睡眠怎麼辦,因爲睡眠的話,有時會造成數據丟失,這方面的可以借鑑前面Suspend的做法,如果更復雜,則牽扯到多線程的同步問題,這部分我會稍後單獨寫一篇文章。

 

前三個問題解決了,該說說最常見的問題,如何在子線程中控制窗體控件。這也是寫線程方面程序經常遇到的,其實我以前寫過兩篇文章,都對這方面做了部分介紹。那麼大家如果有時間的話,不妨去看看。

http://blog.csdn.net/knight94/archive/2006/03/16/626584.aspx

http://blog.csdn.net/knight94/archive/2006/05/27/757351.aspx

 

首先說說,爲什麼不能直接在子線程中操縱UI呢。原因在於子線程和UI線程屬於不同的上下文,換句比較通俗的話說,就好比兩個人在不同的房間裏一樣,那麼要你直接操作另一個房間裏的東西,恐怕不行罷,那麼對於子線程來說也一樣,不能直接操作UI線程中的對象。

 

那麼如何在子線程中操縱UI線程中的對象呢,.Net提供了InvokeBeginInvoke這兩種方法。簡單地說,就是子線程發消息讓UI線程來完成相應的操作。

 

這兩個方法有什麼區別,這在我以前的文章已經說過了,Invoke需要等到所調函數的返回,而BeginInvoke則不需要。

 

用這兩個方法需要注意的,有如下三點:

第一個是由於InvokeBeginInvoke屬於Control類型的成員方法,因此調用的時候,需要得到Control類型的對象才能觸發,也就是說你要觸發窗體做什麼操作或者窗體上某個控件做什麼操作,需要把窗體對象或者控件對象傳遞到線程中。

 

第二個,對於InvokeBeginInvoke接受的參數屬於一個delegate類型,我在以前的文章中使用的是MethodInvoker,這是.Net自帶的一個delegate類型,而並不意味着在使用Invoke或者BeginInvoke的時候只能用它。參看我給的第二篇文章(《如何彈出一個模式窗口來顯示進度條》),會有很多不同的delegate定義。

 

最後一個,使用InvokeBeginInvoke有個需要注意的,就是當子線程在Form_Load開啓的時候,會遇到異常,這是因爲觸發Invoke的對象還沒有完全初始化完畢。處理此類問題,在開啓線程之前顯式的調用“this.Show();”,來使窗體顯示在線程開啓之前。如果此時只是開啓線程來初始化顯示數據,那我建議你不要使用子線程,用Splash窗體的效果可能更好。這方面可以參看如下的例子。

http://www.syncfusion.com/FAQ/WindowsForms/FAQ_c95c.aspx#q621q

 

線程的四個相關問題已經說完了,這篇文章只說了單線程,以及單線程與UI線程交互的問題。其中涉及到的方法不一定是唯一的,因爲.Net還提供了其他類來扶助線程操作,這裏就不一一羅列。至於多線程之間的同步,我會稍後專門寫篇文章進行描述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章