c#线程问题

C#是一门支持多线程的语言,因此线程的使用也是比较常见的。由于线程的知识在Win32编程的时候已经说得过多,所以在.Net中很少介绍这部分(可能.Net不觉得这部分是它所特有的)。

 

那么线程相关的问题大致有如下四类(这篇文章只讨论单线程、单线程与UI线程这两方面的问题)。

问题一,线程的基本操作,例如:暂停、继续、停止等;

问题二,如何向线程传递参数或者从中得到其返回值;

问题三,如何使线程所占用的CPU不要老是百分之百;

最后一个,也是问题最多的,就是如何在子线程来控制UI中的控件,换句话说,就是在线程中控制窗体某些控件的显示。

 

对于问题一,我不建议使用Thread类提供的Suspend、Resume以及Abort这三个方法,前两个有问题,好像在VS05已经屏蔽这两个方法;对于Abort来说,除了资源没有得到及时释放外,有时候会出现异常。如何做呢,通过设置开关变量来完成。

 

对于问题二,我不建议使用静态成员来完成,仅仅为了线程而破坏类的封装有些得不偿失。那如何做呢,通过创建单独的线程类来完成。

 

对 于问题三来说,造成这个原因是由于线程中进行不间断的循环操作,从而使CPU完全被子线程占有。那么处理此类问题,其实很简单,在适当的位置调用 Thread.Sleep(20)来释放所占有CPU资源,不要小看这20毫秒的睡眠,它的作用可是巨大的,可以使其他线程得到CPU资源,从而使你的 CPU使用效率降下来。

 

看完前面的三个问题的解释,对于如何做似乎没有给出一个明确的答案,为了更好地说明如何解决这三个问题,我用一个比较完整的例子展现给大家,代码如下。

Copy code
//--------------------------- 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

    }

}

 

那么对于调用呢,就非常简单了,如下:

       
Copy code
// 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();

 

在回过头来看看前面所说的三个问题。

对于问题一来说,首先需要局部成员的支持,那么

Copy code
        private Mutex mUnique = new Mutex();

 

        private bool blnIsStopped;

        private bool blnSuspended;

        private bool blnStarted;


 

光看成员名称,估计大家都已经猜出其代表的意思。接下来需要修改线程入口函数,要是这些开关变量能发挥作用,那么看看SubThread这个函数。

Copy code
      ///  <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这两个方法,它俩的代码如下。

Copy code
        ///  <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();”才能获得信号量进行继续执行。这样才能达到真正意义上的Suspend和 Resume。

 

至于线程的Start和Stop来说,相对比较简单,这里我就不多说了。

现在再来分析一下问题二,其实例子比较明显,是通过构造函数和属性来完成参数和返回值,这一点我也不多说了。如果线程参数比较多的话,可以考虑属性来完成,类似于返回值。

 

问题三,我就更不用多说了。有人说了,如果子线程中的循环不能睡眠怎么办,因为睡眠的话,有时会造成数据丢失,这方面的可以借鉴前面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提供了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还提供了其他类来扶助线程操作,这里就不一一罗列。至于多线程之间的同步,我会稍后专门写篇文章进行描述。 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
多线程编程中,资源共享是一个常见的问题。当多个线程同时访问和修改共享资源时,如果没有正确的同步机制,就会出现数据竞争和不可预测的结果。以下是一些处理多线程资源共享问题的常用方法: 1. 使用互斥锁(Mutex)或锁(lock):通过在访问共享资源的代码块上加锁,确保同一时间只有一个线程可以访问该资源。这样可以避免数据竞争和并发修改的问题。 ```csharp private static readonly object lockObject = new object(); lock (lockObject) { // 访问共享资源的代码 } ``` 2. 使用线程安全的集合类:在C#中,有一些线程安全的集合类,例如`ConcurrentQueue`、`ConcurrentStack`、`ConcurrentDictionary`等。它们内部实现了适当的同步机制,可以在多线程环境下安全地进行读写操作。 ```csharp ConcurrentQueue<int> queue = new ConcurrentQueue<int>(); // 线程1往队列中添加元素 queue.Enqueue(1); // 线程2从队列中取出元素 int item; if (queue.TryDequeue(out item)) { // 处理取出的元素 } ``` 3. 使用互斥体(Monitor):通过使用`Monitor`类来创建临界区,确保只有一个线程可以进入临界区访问共享资源。 ```csharp private static readonly object lockObject = new object(); Monitor.Enter(lockObject); try { // 访问共享资源的代码 } finally { Monitor.Exit(lockObject); } ``` 4. 使用原子操作:C#提供了一些原子操作的方法,例如`Interlocked`类的方法,可以在多线程环境下进行原子性的读写操作,避免数据竞争和并发修改的问题。 ```csharp private static int counter = 0; Interlocked.Increment(ref counter); // 原子性地增加计数器 ``` 以上方法可以帮助你处理多线程资源共享问题,确保线程安全和数据一致性。根据具体情况选择合适的方法来处理资源共享,以满足程序的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rains卍Soft

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值