您的位置:首页 > 移动开发 > Objective-C

.net线程问题

2008-01-19 17:32 204 查看
C#是一门支持多线程的语言,因此线程的使用也是比较常见的。由于线程的知识在Win32编程的时候已经说得过多,所以在.Net中很少介绍这部分(可能.Net不觉得这部分是它所特有的)。
 
那么线程相关的问题大致有如下四类(这篇文章只讨论单线程、单线程与UI线程这两方面的问题)。
问题一,线程的基本操作,例如:暂停、继续、停止等;
问题二,如何向线程传递参数或者从中得到其返回值;
问题三,如何使线程所占用的CPU不要老是百分之百;
最后一个,也是问题最多的,就是如何在子线程来控制UI中的控件,换句话说,就是在线程中控制窗体某些控件的显示。
 
对于问题一,我不建议使用[b]Thread类提供的Suspend、Resume以及Abort这三个方法,前两个有问题,好像在VS05已经屏蔽这两个方法;对于Abort来说,除了资源没有得到及时释放外,有时候会出现异常。如何做呢,通过设置开关变量来完成。[/b]
 
对于问题二,我不建议使用静态成员来完成,仅仅为了线程而破坏类的封装有些得不偿失。那如何做呢,通过创建单独的线程类来完成。
 
对于问题三来说,造成这个原因是由于线程中进行不间断的循环操作,从而使CPU完全被子线程占有。那么处理此类问题,其实很简单,在适当的位置调用Thread.Sleep(20)来释放所占有CPU资源,不要小看这20毫秒的睡眠,它的作用可是巨大的,可以使其他线程得到CPU资源,从而使你的CPU使用效率降下来。
 
看完前面的三个问题的解释,对于如何做似乎没有给出一个明确的答案,为了更好地说明如何解决这三个问题,我用一个比较完整的例子展现给大家,代码如下。
[align=left]//--------------------------- Sub-thread class ---------------------------------------[/align]
[align=left]//------------------------------------------------------------------------------------[/align]
[align=left]//---File:          clsSubThread[/align]
[align=left]//---Description:   The sub-thread template class file [/align]
[align=left]//---Author:        Knight[/align]
[align=left]//---Date:          Aug.21, 2006[/align]
[align=left]//------------------------------------------------------------------------------------[/align]
[align=left]//---------------------------{Sub-thread class}---------------------------------------[/align]
[align=left]namespace ThreadTemplate[/align]
[align=left]{[/align]
[align=left]    using System;[/align]
[align=left]    using System.Threading;[/align]
[align=left]    using System.IO;[/align]
[align=left]    ///<summary>[/align]
[align=left]    /// Summary description for clsSubThread.[/align]
[align=left]    ///</summary>[/align]
[align=left]    public class clsSubThread:IDisposable[/align]
[align=left]    {[/align]
[align=left]        private Thread thdSubThread = null;[/align]
[align=left]        private Mutex mUnique = new Mutex();[/align]
[align=left] [/align]
[align=left]        private bool blnIsStopped;[/align]
[align=left]        private bool blnSuspended;[/align]
[align=left]        private bool blnStarted;[/align]
[align=left]        private int nStartNum;[/align]
[align=left] [/align]
[align=left]        public bool IsStopped[/align]
[align=left]        {[/align]
[align=left]            get{ return blnIsStopped; }[/align]
[align=left]        }[/align]
[align=left]        public bool IsSuspended[/align]
[align=left]        {[/align]
[align=left]            get{ return blnSuspended; }[/align]
[align=left]        }[/align]
[align=left]        public int ReturnValue[/align]
[align=left]        {[/align]
[align=left]            get{ return nStartNum;}[/align]
[align=left]        }[/align]
[align=left] [/align]
[align=left]    [/align]
[align=left]        public clsSubThread( int StartNum )[/align]
[align=left]        {[/align]
[align=left]            //[/align]
[align=left]            // TODO: Add constructor logic here[/align]
[align=left]            //[/align]
[align=left]            blnIsStopped = true;[/align]
[align=left]            blnSuspended = false;[/align]
[align=left]            blnStarted = false;[/align]
[align=left]            [/align]
[align=left]            nStartNum = StartNum;[/align]
[align=left]        }[/align]
[align=left] [/align]
[align=left]        ///<summary>[/align]
[align=left]        /// Start sub-thread[/align]
[align=left]        ///</summary>[/align]
[align=left]        public void Start()[/align]
[align=left]        {[/align]
[align=left]            if( !blnStarted )[/align]
[align=left]            {[/align]
[align=left]                thdSubThread = new Thread( new ThreadStart( SubThread ) );[/align]
[align=left]                blnIsStopped = false;[/align]
[align=left]                blnStarted = true;[/align]
[align=left]                thdSubThread.Start();[/align]
[align=left]            }[/align]
[align=left]        }[/align]
[align=left] [/align]
[align=left]        ///<summary>[/align]
[align=left]        /// Thread entry function[/align]
[align=left]        ///</summary>[/align]
[align=left]        private void SubThread()[/align]
[align=left]        {[/align]
[align=left]            do[/align]
[align=left]            {[/align]
[align=left]                // Wait for resume-command if got suspend-command here [/align]
[align=left]                mUnique.WaitOne();[/align]
[align=left]                mUnique.ReleaseMutex();[/align]
[align=left] [/align]
[align=left]                nStartNum++;[/align]
[align=left]            [/align]
[align=left]                Thread.Sleep(1000); // Release CPU here[/align]
[align=left]            }while( blnIsStopped == false );[/align]
[align=left]        }[/align]
[align=left] [/align]
[align=left]        ///<summary>[/align]
[align=left]        /// Suspend sub-thread[/align]
[align=left]        ///</summary>[/align]
[align=left]        public void Suspend()[/align]
[align=left]        {[/align]
[align=left]            if( blnStarted && !blnSuspended )[/align]
[align=left]            {[/align]
[align=left]                blnSuspended = true;[/align]
[align=left]                mUnique.WaitOne();[/align]
[align=left]            }[/align]
[align=left]        }[/align]
[align=left]    [/align]
[align=left]        ///<summary>[/align]
[align=left]        /// Resume sub-thread[/align]
[align=left]        ///</summary>[/align]
[align=left]        public void Resume()[/align]
[align=left]        {[/align]
[align=left]            if( blnStarted && blnSuspended )[/align]
[align=left]            {[/align]
[align=left]                blnSuspended = false;[/align]
[align=left]                mUnique.ReleaseMutex();[/align]
[align=left]            }[/align]
[align=left]        }[/align]
[align=left] [/align]
[align=left]        ///<summary>[/align]
[align=left]        /// Stop sub-thread[/align]
[align=left]        ///</summary>[/align]
[align=left]        public void Stop()[/align]
[align=left]        {[/align]
[align=left]            if( blnStarted )[/align]
[align=left]            {[/align]
[align=left]                if( blnSuspended )[/align]
[align=left]                    Resume();[/align]
[align=left] [/align]
[align=left]                blnStarted = false;[/align]
[align=left]                blnIsStopped = true;[/align]
[align=left]                thdSubThread.Join();[/align]
[align=left]            }[/align]
[align=left]        }[/align]
[align=left]        #region IDisposable Members[/align]
[align=left]        ///<summary>[/align]
[align=left]        /// Class resources dispose here[/align]
[align=left]        ///</summary>[/align]
[align=left]        public void Dispose()[/align]
[align=left]        {[/align]
[align=left]            // TODO: Add clsSubThread.Dispose implementation[/align]
[align=left]            Stop();//Stop thread first[/align]
[align=left]            GC.SuppressFinalize( this );[/align]
[align=left]        }[/align]
[align=left] [/align]
[align=left]        #endregion[/align]
[align=left]    }[/align]
[align=left]}[/align]
 
那么对于调用呢,就非常简单了,如下:
[align=left]        // Create new sub-thread object with parameters[/align]
[align=left]        clsSubThread mySubThread = new clsSubThread( 5 );[/align]
[align=left] [/align]
[align=left]        mySubThread.Start();//Start thread[/align]
[align=left]        [/align]
[align=left]        Thread.Sleep( 2000 );[/align]
[align=left]        mySubThread.Suspend();//Suspend thread [/align]
[align=left] [/align]
[align=left]        Thread.Sleep( 2000 );[/align]
[align=left]        mySubThread.Resume();//Resume thread [/align]
[align=left] [/align]
[align=left]        Thread.Sleep( 2000 );[/align]
[align=left]        mySubThread.Stop();//Stop thread [/align]
[align=left] [/align]
[align=left]        //Get thread's return value[/align]
[align=left]        Debug.WriteLine( mySubThread.ReturnValue );[/align]
[align=left] [/align]
[align=left]        //Release sub-thread object[/align]
[align=left]        mySubThread.Dispose();[/align]
 
在回过头来看看前面所说的三个问题。
对于问题一来说,首先需要局部成员的支持,那么
[align=left]        private Mutex mUnique = new Mutex();[/align]
[align=left] [/align]
[align=left]        private bool blnIsStopped;[/align]
[align=left]        private bool blnSuspended;[/align]
[align=left]        private bool blnStarted;[/align]
 
光看成员名称,估计大家都已经猜出其代表的意思。接下来需要修改线程入口函数,要是这些开关变量能发挥作用,那么看看SubThread这个函数。
[align=left]        ///<summary>[/align]
[align=left]        /// Thread entry function[/align]
[align=left]        ///</summary>[/align]
[align=left]        private void SubThread()[/align]
[align=left]        {[/align]
[align=left]            do[/align]
[align=left]            {[/align]
[align=left]                // Wait for resume-command if got suspend-command here [/align]
[align=left]                mUnique.WaitOne();[/align]
[align=left]                mUnique.ReleaseMutex();[/align]
[align=left] [/align]
[align=left]                nStartNum++;[/align]
[align=left]            [/align]
[align=left]                Thread.Sleep(1000);[/align]
[align=left]            }while( blnIsStopped == false );[/align]
[align=left]        }[/align]
 
函数比较简单,不到十句,可能对于“blnIsStopped == false”这个判断来说,大家还比较好理解,这是一个普通的判断,如果当前Stop开关打开了,就停止循环;否则一直循环。
大家比较迷惑的可能是如下这两句:
[align=left]                mUnique.WaitOne();[/align]
[align=left]                mUnique.ReleaseMutex();[/align]
这两句的目的是为了使线程在Suspend操作的时候能发挥效果,为了解释这两句,需要结合Suspend和Resume这两个方法,它俩的代码如下。
[align=left]        ///<summary>[/align]
[align=left]        /// Suspend sub-thread[/align]
[align=left]        ///</summary>[/align]
[align=left]        public void Suspend()[/align]
[align=left]        {[/align]
[align=left]            if( blnStarted && !blnSuspended )[/align]
[align=left]            {[/align]
[align=left]                blnSuspended = true;[/align]
[align=left]                mUnique.WaitOne();[/align]
[align=left]            }[/align]
[align=left]        }[/align]
[align=left]    [/align]
[align=left]        ///<summary>[/align]
[align=left]        /// Resume sub-thread[/align]
[align=left]        ///</summary>[/align]
[align=left]        public void Resume()[/align]
[align=left]        {[/align]
[align=left]            if( blnStarted && blnSuspended )[/align]
[align=left]            {[/align]
[align=left]                blnSuspended = false;[/align]
[align=left]                mUnique.ReleaseMutex();[/align]
[align=left]            }[/align]
[align=left]        }[/align]
 
为了更好地说明,还需要先简单说说Mutex类型。对于此类型对象,当调用对象的WaitOne之后,如果此时没有其他线程对它使用的时候,就立刻获得信号量,继续执行代码;当再调用ReleaseMutex之前,如果再调用对象的WaitOne方法,就会一直等待,直到获得信号量的调用ReleaseMutex来进行释放。这就好比卫生间的使用,如果没有人使用则可以直接使用,否则只有等待。
明白了这一点后,再来解释这两句所能出现的现象。
[align=left]                mUnique.WaitOne();[/align]
[align=left]                mUnique.ReleaseMutex();[/align]
 
当在线程函数中,执行到“mUnique.WaitOne();”这一句的时候,如果此时外界没有发送Suspend消息,也就是信号量没有被占用,那么这一句可以立刻返回。那么为什么要紧接着释放呢,因为不能总占着信号量,立即释放信号量是避免在发送Suspend命令的时候出现等待;如果此时外界已经发送了Suspend消息,也就是说信号量已经被占用,此时“mUnique.WaitOne();”不能立刻返回,需要等到信号量被释放才能继续进行,也就是需要调用Resume的时候,“mUnique.WaitOne();”才能获得信号量进行继续执行。这样才能达到真正意义上的Suspend和Resume。
 
至于线程的Start和Stop来说,相对比较简单,这里我就不多说了。
 
现在再来分析一下问题二,其实例子比较明显,是通过构造函数和属性来完成参数和返回值,这一点我也不多说了。如果线程参数比较多的话,可以考虑属性来完成,类似于返回值。
 
问题三,我就更不用多说了。有人说了,如果子线程中的循环不能睡眠怎么办,因为睡眠的话,有时会造成数据丢失,这方面的可以借鉴前面[b]Suspend的做法,如果更复杂,则牵扯到多线程的同步问题,这部分我会稍后单独写一篇文章。[/b]
 
前三个问题解决了,该说说最常见的问题,如何在子线程中控制窗体控件。这也是写线程方面程序经常遇到的,其实我以前写过两篇文章,都对这方面做了部分介绍。那么大家如果有时间的话,不妨去看看。
http://blog.csdn.net/knight94/archive/2006/03/16/626584.aspx
http://blog.csdn.net/knight94/archive/2006/05/27/757351.aspx
 
首先说说,为什么不能直接在子线程中操纵UI呢。原因在于子线程和[b]UI线程属于不同的上下文,换句比较通俗的话说,就好比两个人在不同的房间里一样,那么要你直接操作另一个房间里的东西,恐怕不行罢,那么对于子线程来说也一样,不能直接操作UI线程中的对象。[/b]
 
那么如何在子线程中操纵UI线程中的对象呢,.Net提供了Invoke和BeginInvoke这两种方法。简单地说,就是子线程发消息让UI线程来完成相应的操作。
 
这两个方法有什么区别,这在我以前的文章已经说过了,Invoke需要等到所调函数的返回,而BeginInvoke则不需要。
 
用这两个方法需要注意的,有如下三点:
第一个是由于Invoke和BeginInvoke属于Control类型的成员方法,因此调用的时候,需要得到Control类型的对象才能触发,也就是说你要触发窗体做什么操作或者窗体上某个控件做什么操作,需要把窗体对象或者控件对象传递到线程中。
 
第二个,对于Invoke和BeginInvoke接受的参数属于一个delegate类型,我在以前的文章中使用的是MethodInvoker,这是.Net自带的一个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。除此外,还要增加一个方法,用来控制进度条的增加幅度,具体如下:
[align=left]    ///<summary>[/align]
[align=left]    /// Increase process bar[/align]
[align=left]    ///</summary>[/align]
[align=left]    ///<param name="nValue">the value increased</param>[/align]
[align=left]    ///<returns></returns>[/align]
[align=left]    public bool Increase( int nValue )[/align]
[align=left]    {[/align]
[align=left]        if( nValue > 0 )[/align]
[align=left]        {[/align]
[align=left]            if( prcBar.Value + nValue < prcBar.Maximum )[/align]
[align=left]            {[/align]
[align=left]                prcBar.Value += nValue;[/align]
[align=left]                return true;[/align]
[align=left]            }[/align]
[align=left]            else[/align]
[align=left]            {[/align]
[align=left]                prcBar.Value = prcBar.Maximum;[/align]
[align=left]                this.Close();[/align]
[align=left]                return false;[/align]
[align=left]            }[/align]
[align=left]        }[/align]
[align=left]        return false;[/align]
[align=left]    }[/align]
 
接着就是主窗体了,如何进行操作了,首先需要定义两个私有成员,一个委托。其中一个私有成员是保存当前进度条窗体对象,另一个是保存委托方法(即增加进度条尺度),具体如下:
[align=left]    private frmProcessBar myProcessBar = null;[/align]
[align=left]    private delegate bool IncreaseHandle( int nValue );[/align]
[align=left]    private IncreaseHandle myIncrease = null;[/align]
 
接着要在主窗体中提供函数来打开进度条窗体,如下:
[align=left]    ///<summary>[/align]
[align=left]    /// Open process bar window[/align]
[align=left]    ///</summary>[/align]
[align=left]    private void ShowProcessBar()[/align]
[align=left]    {[/align]
[align=left]        myProcessBar = new frmProcessBar();[/align]
[align=left] [/align]
[align=left]        // Init increase event[/align]
[align=left]        myIncrease = new IncreaseHandle( myProcessBar.Increase );[/align]
[align=left]        myProcessBar.ShowDialog();[/align]
[align=left]        myProcessBar = null;[/align]
[align=left]    }[/align]
 
那么现在就可以开始创建线程来运行,具体如下:
[align=left]    ///<summary>[/align]
[align=left]    /// Sub thread function[/align]
[align=left]    ///</summary>[/align]
[align=left]    private void ThreadFun()[/align]
[align=left]    {[/align]
[align=left]        MethodInvoker mi = new MethodInvoker( ShowProcessBar );[/align]
[align=left]        this.BeginInvoke( mi );[/align]
[align=left] [/align]
[align=left]        Thread.Sleep( 1000 );//Sleep a while to show window[/align]
[align=left] [/align]
[align=left]        bool blnIncreased = false;[/align]
[align=left]        object objReturn = null;[/align]
[align=left]        do[/align]
[align=left]        {[/align]
[align=left]            Thread.Sleep( 50 );[/align]
[align=left]            objReturn = this.Invoke( this.myIncrease, [/align]
[align=left]                new object[]{ 2 } );[/align]
[align=left]            blnIncreased = (bool)objReturn ;[/align]
[align=left]        }[/align]
[align=left]        while( blnIncreased );[/align]
[align=left]    }[/align]
      
       注意以上,在打开进度条窗体和增加进度条进度的时候,一个用的是BeginInvoke,一个是Invoke,这里的区别是BeginInvoke不需要等待方法运行完毕,而Invoke是要等待方法运行完毕。还有一点,此处用返回值来判断进度条是否到头了,如果需要有其他的控制,可以类似前面的方法来进行扩展。
 
启动线程,可以如下:
[align=left]    Thread thdSub = new Thread( new ThreadStart( ThreadFun ) );[/align]
[align=left]    thdSub.Start();[/align]
 
这样,一个用模式打开进度条窗体就做完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息