c#線程Thread示例
C#是一門支持多線程的語言,因此線程的使用也是比較常見的。由于線程的知識(shí)在Win32編程的時(shí)候已經(jīng)說得過多,所以在.Net中很少介紹這部分(可能.Net不覺得這部分是它所特有的)。
那么線程相關(guān)的問題大致有如下四類(這篇文章只討論單線程、單線程與UI線程這兩方面的問題)。
問題一,線程的基本操作,例如:暫停、繼續(xù)、停止等;
問題二,如何向線程傳遞參數(shù)或者從中得到其返回值;
問題三,如何使線程所占用的CPU不要老是百分之百;
最后一個(gè),也是問題最多的,就是如何在子線程來控制UI中的控件,換句話說,就是在線程中控制窗體某些控件的顯示。
對于問題一,我不建議使用Thread類提供的Suspend、Resume以及Abort這三個(gè)方法,前兩個(gè)有問題,好像在VS05已經(jīng)屏蔽這兩個(gè)方法;對于Abort來說,除了資源沒有得到及時(shí)釋放外,有時(shí)候會(huì)出現(xiàn)異常。如何做呢,通過設(shè)置開關(guān)變量來完成。
對于問題二,我不建議使用靜態(tài)成員來完成,僅僅為了線程而破壞類的封裝有些得不償失。那如何做呢,通過創(chuàng)建單獨(dú)的線程類來完成。
對于問題三來說,造成這個(gè)原因是由于線程中進(jìn)行不間斷的循環(huán)操作,從而使CPU完全被子線程占有。那么處理此類問題,其實(shí)很簡單,在適當(dāng)?shù)奈恢谜{(diào)用Thread.Sleep(20)來釋放所占有CPU資源,不要小看這20毫秒的睡眠,它的作用可是巨大的,可以使其他線程得到CPU資源,從而使你的CPU使用效率降下來。
看完前面的三個(gè)問題的解釋,對于如何做似乎沒有給出一個(gè)明確的答案,為了更好地說明如何解決這三個(gè)問題,我用一個(gè)比較完整的例子展現(xiàn)給大家,代碼如下。
//--------------------------- Sub-thread class ---------------------------------------
//------------------------------------------------------------------------------------
//---File: clsSubThread
//---Description: The sub-thread template class file
//---Author: Knight
//------------------------------------------------------------------------------------
//---------------------------{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
}
}
那么對于調(diào)用呢,就非常簡單了,如下:
// 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();
在回過頭來看看前面所說的三個(gè)問題。
對于問題一來說,首先需要局部成員的支持,那么
private Mutex mUnique = new Mutex();
private bool blnIsStopped;
private bool blnSuspended;
private bool blnStarted;
光看成員名稱,估計(jì)大家都已經(jīng)猜出其代表的意思。接下來需要修改線程入口函數(shù),要是這些開關(guān)變量能發(fā)揮作用,那么看看SubThread這個(gè)函數(shù)。
/// <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 );
}
函數(shù)比較簡單,不到十句,可能對于“blnIsStopped == false”這個(gè)判斷來說,大家還比較好理解,這是一個(gè)普通的判斷,如果當(dāng)前Stop開關(guān)打開了,就停止循環(huán);否則一直循環(huán)。
大家比較迷惑的可能是如下這兩句:
mUnique.WaitOne();
mUnique.ReleaseMutex();
這兩句的目的是為了使線程在Suspend操作的時(shí)候能發(fā)揮效果,為了解釋這兩句,需要結(jié)合Suspend和Resume這兩個(gè)方法,它倆的代碼如下。
/// <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類型。對于此類型對象,當(dāng)調(diào)用對象的WaitOne之后,如果此時(shí)沒有其他線程對它使用的時(shí)候,就立刻獲得信號(hào)量,繼續(xù)執(zhí)行代碼;當(dāng)再調(diào)用ReleaseMutex之前,如果再調(diào)用對象的WaitOne方法,就會(huì)一直等待,直到獲得信號(hào)量的調(diào)用ReleaseMutex來進(jìn)行釋放。這就好比衛(wèi)生間的使用,如果沒有人使用則可以直接使用,否則只有等待。
明白了這一點(diǎn)后,再來解釋這兩句所能出現(xiàn)的現(xiàn)象。
mUnique.WaitOne();
mUnique.ReleaseMutex();
當(dāng)在線程函數(shù)中,執(zhí)行到“mUnique.WaitOne();”這一句的時(shí)候,如果此時(shí)外界沒有發(fā)送Suspend消息,也就是信號(hào)量沒有被占用,那么這一句可以立刻返回。那么為什么要緊接著釋放呢,因?yàn)椴荒芸傉贾盘?hào)量,立即釋放信號(hào)量是避免在發(fā)送Suspend命令的時(shí)候出現(xiàn)等待;如果此時(shí)外界已經(jīng)發(fā)送了Suspend消息,也就是說信號(hào)量已經(jīng)被占用,此時(shí)“mUnique.WaitOne();”不能立刻返回,需要等到信號(hào)量被釋放才能繼續(xù)進(jìn)行,也就是需要調(diào)用Resume的時(shí)候,“mUnique.WaitOne();”才能獲得信號(hào)量進(jìn)行繼續(xù)執(zhí)行。這樣才能達(dá)到真正意義上的Suspend和Resume。
至于線程的Start和Stop來說,相對比較簡單,這里我就不多說了。
現(xiàn)在再來分析一下問題二,其實(shí)例子比較明顯,是通過構(gòu)造函數(shù)和屬性來完成參數(shù)和返回值,這一點(diǎn)我也不多說了。如果線程參數(shù)比較多的話,可以考慮屬性來完成,類似于返回值。
問題三,我就更不用多說了。有人說了,如果子線程中的循環(huán)不能睡眠怎么辦,因?yàn)樗叩脑?,有時(shí)會(huì)造成數(shù)據(jù)丟失,這方面的可以借鑒前面Suspend的做法,如果更復(fù)雜,則牽扯到多線程的同步問題,這部分我會(huì)稍后單獨(dú)寫一篇文章。
前三個(gè)問題解決了,該說說最常見的問題,如何在子線程中控制窗體控件。這也是寫線程方面程序經(jīng)常遇到的。
首先說說,為什么不能直接在子線程中操縱UI呢。原因在于子線程和UI線程屬于不同的上下文,換句比較通俗的話說,就好比兩個(gè)人在不同的房間里一樣,那么要你直接操作另一個(gè)房間里的東西,恐怕不行罷,那么對于子線程來說也一樣,不能直接操作UI線程中的對象。
那么如何在子線程中操縱UI線程中的對象呢,.Net提供了Invoke和BeginInvoke這兩種方法。簡單地說,就是子線程發(fā)消息讓UI線程來完成相應(yīng)的操作。
這兩個(gè)方法有什么區(qū)別,這在我以前的文章已經(jīng)說過了,Invoke需要等到所調(diào)函數(shù)的返回,而BeginInvoke則不需要。
用這兩個(gè)方法需要注意的,有如下三點(diǎn):
第一個(gè)是由于Invoke和BeginInvoke屬于Control類型的成員方法,因此調(diào)用的時(shí)候,需要得到Control類型的對象才能觸發(fā),也就是說你要觸發(fā)窗體做什么操作或者窗體上某個(gè)控件做什么操作,需要把窗體對象或者控件對象傳遞到線程中。
第二個(gè),對于Invoke和BeginInvoke接受的參數(shù)屬于一個(gè)delegate類型,我在以前的文章中使用的是MethodInvoker,這是.Net自帶的一個(gè)delegate類型,而并不意味著在使用Invoke或者BeginInvoke的時(shí)候只能用它。參看我給的第二篇文章(《如何彈出一個(gè)模式窗口來顯示進(jìn)度條》),會(huì)有很多不同的delegate定義。
最后一個(gè),使用Invoke和BeginInvoke有個(gè)需要注意的,就是當(dāng)子線程在Form_Load開啟的時(shí)候,會(huì)遇到異常,這是因?yàn)橛|發(fā)Invoke的對象還沒有完全初始化完畢。處理此類問題,在開啟線程之前顯式的調(diào)用“this.Show();”,來使窗體顯示在線程開啟之前。如果此時(shí)只是開啟線程來初始化顯示數(shù)據(jù),那我建議你不要使用子線程,用Splash窗體的效果可能更好。
線程的四個(gè)相關(guān)問題已經(jīng)說完了,這篇文章只說了單線程,以及單線程與UI線程交互的問題。其中涉及到的方法不一定是唯一的,因?yàn)?Net還提供了其他類來扶助線程操作,這里就不一一羅列。
相關(guān)文章
Unity UGUI教程之實(shí)現(xiàn)滑頁效果
使用UGUI提供的ScrollRect和ScrollBar組件實(shí)現(xiàn)基本滑動(dòng)以及自己控制每次移動(dòng)一頁來達(dá)到滑頁的效果。具體實(shí)現(xiàn)思路請參考下本教程2016-04-04C#同步網(wǎng)絡(luò)時(shí)間的方法實(shí)例詳解
這篇文章主要介紹了C#同步網(wǎng)絡(luò)時(shí)間的方法,以實(shí)例形式較為詳細(xì)的分析了C#獲取網(wǎng)絡(luò)時(shí)間與同步本機(jī)系統(tǒng)時(shí)間的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-05-05Unity中C#和Java的相互調(diào)用實(shí)例代碼
在unity中接入sdk或者定制一些功能時(shí),需要調(diào)用系統(tǒng)接口。安卓手機(jī)實(shí)際操作中,也就是Unity與android相互調(diào)用。我們在Unity中使用c#,android中使用java。2018-02-02C#中類與結(jié)構(gòu)的區(qū)別實(shí)例分析
這篇文章主要介紹了C#中類與結(jié)構(gòu)的區(qū)別,類與結(jié)構(gòu)是C#初學(xué)者比較輕易混淆的概念,本文加以實(shí)例說明,需要的朋友可以參考下2014-08-08Unity?UGUI的MaskableGraphic可遮罩圖形組件介紹使用
這篇文章主要為大家介紹了Unity?UGUI的MaskableGraphic可遮罩圖形組件介紹使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07C#之Windows自帶打印功能的實(shí)現(xiàn)
這篇文章主要介紹了C#之Windows自帶打印功能的實(shí)現(xiàn)方式,具有很好的價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06