------------
//------------------------------------------------------------------------------------
//---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 )
{
&nbs 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
///]
yle="COLOR: gray"></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 = truen>;
thdSubThread.Join();
}
}
#region IDisposable Members
///<summary>
/// Cl 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();
在回過頭來看看前面所說的三個問題。
對於問題一來說,首先需要局部成員的支持,那麼
="COLOR: blue">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操作的時候能發揮效果,為了解釋這兩句,需要結合Suspend和Resume這兩個方法,它倆的代碼如下。
///<summary>
/// Suspend sub-thread
///</summary>
public void Suspend()
{
if( blnStarted && !blnSuspended )
{
blnSuspended = true;
mUnique.WaitOne();
&nbs }
}
///<summary>
/// Resume sub-thread
///</summary>
public void Resume()
{
if( blnStarted && blnSuspended )
{
blnSuspended = false;
mUnique.ReleaseMutex();n > }
}
為了更好地說明,還需要先簡單說說Mutex類型。對於此類型對象,當調用對象的WaitOne之後,如果此時沒有其他線程對它使用的時候,就立刻獲得信號量,繼續執行代碼;當再調用ReleaseMutex之前,如果再調用對象的WaitOne方法,就會一直等待,直到獲得信號量的調用ReleaseMutex來進行釋放。這就好比衛生間的使用,如果沒有人使用則可以直接使用,否則只有等待。
明白了這一點後,再來解釋這兩句所能出現的現象。
mUnique.WaitOne();
mUnique.ReleaseMutex();
當在線程函數中,執行到“mUnique.WaitOne();”這一句的時候,如果此時外界沒有發送Suspend消息,也就是信號量沒有被占用,那麼這一句可以立刻返回。那麼為什麼要緊接著釋放呢,因為不能總占著信號量,立即釋放信號量是避免在發送Suspend命令的時候出現等待;如果此時外界已經發送了Suspend消息,也就是說信號量已經被占用,此時“mUnique.WaitOne();”不能立刻返回,需要等到信號量被釋放才能繼續進行,也就是需要調用Resume的時候,“mUnique.WaitOne();”才能獲得信號量進行繼續執行。這樣才能達到真正意義上的Suspend和Resume。
至於線程的Start和Stop來說,相對比較簡單,這裡我就不多說了。
現在再來分析一下問題二,其實例子比較明顯,是通過構造函數和屬性來完成參數和返回值,這一點我也不多說了。如果線程參數比較多的話,可以考慮屬性來完成,類似於返回值。
問題三,我就更不用多說了。有人說了,如果子線程中的循環不能睡眠怎麼辦,因為睡眠的話,有時會造成數據丟失,這方面的可以借鑒前面Suspend的做法,如果更復雜,則牽扯到多線程的同步問題,這部分我會稍後單獨寫一篇文章。
前三個問題解決了,該說說最常見的問題,如何在子線程中控制窗體控件。這也是寫線程方面程序經常遇到的,其實我以前寫過兩篇文章,都對這方面做了部分介紹。那麼大家如果有時間的話,不妨去看看。
http://blog.csdn.net/knight94/archive/2006/05/27/757351.aspx'>http://blog.csdn.Net/knight94/archive/2006/03/16/626584.ASPx/knight94/archive/2006/05/27/757351.ASPx">http://blog.csdn.net/knight94/archive/2006/05/27/757351.aspx
首先說說,為什麼不能直接在子線程中操縱UI呢。原因在於子線程和UI線程屬於不同的上下文,換句比較通俗的話說,就好比兩個人在不同的房間裡一樣,那麼要你直接操作另一個房間裡的東西,恐怕不行罷,那麼對於子線程來說也一樣,不能直接操作UI線程中的對象。
那麼如何在子線程中操縱UI線程中的對象呢,.Net提供了Invoke和BeginInvoke這兩種方法。簡單地說,就是子線程發消息讓UI線程來完成相應的操作。
這兩個方法有什麼區別,這在我以前的文章已經說過了,Invoke需要等到所調函數的返回,而BeginInvoke則不需要。
用這兩個方法需要注意的,有如下三點:
第一個是由於Invoke和BeginInvoke屬於Control類型的成員方法,因此調用的時候,需要得到Control類型的對象才能觸發,也就是說你要觸發窗體做什麼操作或者窗體上某個控件做什麼操作,需要把窗體對象或者控件對象傳遞到線程中。
第二個,對於Invoke和BeginInvoke接受的參數屬於一個delegate類型,我在以前的文章中使用的是MethodInvoker,這是.Net<strong>自帶的一個delegate類型,而並不意味著在使用Invoke或者BeginInvoke的時候只能用它。參看我給的第二篇文章(《如何彈出一個模式窗口來顯示進度條》),會有很多不同的delegate定義。
最後一個,使用Invoke和BeginInvoke有個需要注意的,就是當子線程在Form_Load開啟的時候,會遇到異常,這是因為觸發Invoke的對象還沒有完全初始化完畢。處理此類問題,在開啟線程之前顯式的調用“this.Show();”,來使窗體顯示在線程開啟之前。如果此時只是開啟線程來初始化顯示數據,那我建議你不要使用子線程,用Splash窗體的效果可能更好。這方面可以參看如下的例子。
http://www.syncfusion.com/FAQ/WindowsForms/FAQ_c95c.ASPx#q621q
線程的四個相關問題已經說完了,這篇文章只說了單線程,以及單線程與UI線程交互的問題。其中涉及到的方法不一定是唯一的,因為.Net還提供了其他類來扶助線程操作,這裡就不一一羅列。至於多線程之間的同步,我會稍後專門寫篇文章進行描述。
如何彈出一個模式窗口來顯示進度條
最近看了好多人問這方面的問題,以前我也寫過一篇blog,裡面說了如何在子線程中控制進度條。但目前大多數環境,需要彈出模式窗口,來顯示進度條,那麼只需要在原先的基礎上稍作修改即可。
首先是進度條窗體,需要在上面添加進度條,然後去掉ControlBox。除此外,還要增加一個方法,用來控制進度條的增加幅度,具體如下:
///<summary>//
span > Increase process bar
///</summary>
///<param name="nValue">the value increased</param>
///<returns></returns>
public bool Increase( int nValue )
{
if( nValue > 0 )
{
if( prcBar.Value + nValue < prcBar.Maximum )
{
prcBar.Value += nValue;
return true;
}
else
{
prcBar.Value = prcBar.Maximum;
pan > this.Close();
return false;
}
}
return false;
}
接著就是主窗體了,如何進行操作了,首先需要定義兩個私有成員,一個委托。其中一個私有成員是保存當前進度條窗體對象,另一個是保存委托方法(即增加進度條尺度),具體如下:
private frmProcessBar myProcessBar = null;
private delegate bool IncreaseHandle( int nValue );
private IncreaseHandle myIncrease = null;
接著要在主窗體中提供函數來打開進度條窗體,如下:
///<summary>
/// Open process bar window
///</summary>
private void ShowProcessBar()
{
myProcessBar = new frmProcessBar();
// Init increase event
myIncrease = new IncreaseHandle( myProcessBar.Increase );
myProcessBar.ShowDialog();
myProcessBar = null;
}
那麼現在就可以開始創建線程來運行,具體如下:
///<summary>
/// Sub thread function
///</summary>
private void ThreadFun()
{
MethodInvoker mi = new MethodInvoker( ShowProcessBar );
this.BeginInvoke( mi );
Thread.Sleep( 1000 );//Sleep a while to show window
bool blnIncreased = false;
object objReturn = null;
do
{
Thread.Sleep( 50 );
objReturn = this.Invoke( this.myIncrease,
new object[]{ 2 } );
blnIncreased = (bool)objReturn ;
}
while( blnIncreased );
}
注意以上,在打開進度條窗體和增加進度條進度的時候,一個用的是BeginInvoke,一個是Invoke,這裡的區別是BeginInvoke不需要等待方法運行完畢,而Invoke是要等待方法運行完畢。還有一點,此處用返回值來判斷進度條是否到頭了,如果需要有其他的控制,可以類似前面的方法來進行擴展。文章整理:
啟動線程,可以如下:
Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );
thdSub.Start();
這樣,一個用模式打開進度條窗體就做完了。