多線程的返回值等問題


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

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

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

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

        //委托
        public delegate double weiTuo(double a, double b);
        weiTuo w;

        private void btn_Begin_Click(object sender, EventArgs e)
        {
            //Thread th_thread = new Thread();
            w = new weiTuo(getCount);
            //異步調用中的參數和返回值 
            AsyncCallback callback = new AsyncCallback(AsyncCallbackImpl);
            w.BeginInvoke(1, 2, callback, null);

        }
        //線程調用的方法
        double getCount(double a, double b)
        {
            return a + b;
        }
        /// <summary>
        /// 線程完成之后回調的函數
        /// </summary>
        /// <param name="ar"></param>
        public void AsyncCallbackImpl(IAsyncResult ar)
        {
            //獲取執行完后的返回值
            double re = w.EndInvoke(ar);
            MessageBox.Show(" " + ar.AsyncState);
        }
Code

當線程的ThreadState==ThreadState.Stop時,一般就說明線程完成了工作,這時結果就可用了,如果不是這個狀態,就繼續執行別的工作,或者等待一會,然后再嘗試.倘若需要等有多個子線程需的返回,並且需要用他們的結果來進行進異步計算,那就叫做線程同步了,下面我們介紹另外一種我比較推薦的方法,能夠自定義參數個數,並且返回數據,而且使用起來也相對方便

使用委托的異步調用方法和回調

首先我們要把需要異步調用的方法定義為一個委托,然后利用BeginInvoke來異步調用,BeginInvoke的第一個參數就是直徑,第二個是當線程執行完畢后的調用的方法

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

 

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

對於上面三個問題用一個例子呈現給大家:

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();
Code

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

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

 

private Mutex mUnique = new Mutex();
        private bool blnIsStopped;
        private bool blnSuspended;
        private bool blnStarted;
Code

 

光看成員名稱,估計大家都已經猜出其代表的意思。接下來需要修改線程入口函數,要是這些開關變量能發揮作用,那么看看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 );
        }
Code

 

函數比較簡單,不到十句,可能對於“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();
            }
        }
Code

 

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

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

                mUnique.WaitOne();

                mUnique.ReleaseMutex();

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

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

問題三,我就更不用多說了。有人說了,如果子線程中的循環不能睡眠怎么辦,因為睡眠的話,有時會造成數據丟失,這方面的可以借鑒前面Suspend的做法,

 

為什么不能直接在子線程中操縱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窗體的效果可能更好。

 150279198---加QQ群聊--大神勿擾。--菜鳥營


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM