代码改变世界

三、线程

2014-05-21 16:44  xchit  阅读(357)  评论(0编辑  收藏  举报

  线程

  线程(thread)是进程中某个单一顺序的控制流。也被称为轻量进程。计算机科学术语,指运行中的程序的调度单位。将一个进程划分为若干个独立的执行流,每一个执行流均称为一个线程。

  (1)线程是CPU调度和分配的基本单位。

  (2)每个进程都有一个主线程。

  (3)除了主线程以外,还可以给一个进程分配若干个子线程,从而达到多个任务并行执行的目的。

  Thread类位于System.Threading命名空间下。

  Thread类是用于创建和控制线程的,对线程的常用操作有:启动线程终止线程合并线程让线程休眠等。

  1、Thread类提供的常用属性

  IsAlive属性:获取一个值,该值指示当前线程的执行状态。如果此线程已启动并且尚未正常终止,则为true;否则为false

  IsBackground属性:获取或设置一个值,该值指示某个线程是否为后台线程。是后台线程或即将成为后台线程,则为true;否则为false

  Priority属性:获取或设置一个值,该值指示线程的调度优先级

  2、Thread类提供的常用方法

  Start方法:启动线程

  Join方法:将指定的线程合并到当前线程中,并阻止当前线程执行,直到指定的线程终止或经过了指定的时间为止

  Sleep方法:将当前线程阻止指定的毫秒数,零(0)表示应挂起此线程以使其他等待线程能够执行

  Abort方法:在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程

  前台线程与后台线程

  一个线程要么是后台线程要么是前台线程。

  后台线程与前台线程类似,区别是后台线程不会影响进程终止。属于某个进程的所有前台线程都终止后,公共语言运行库就会结束该进程,而且所有属于该进程的后台线程也都会立即停止,而不管后台工作是否完成。

  利用Thread对象的IsBackground属性,可以设置或判断一个线程是后台线程还是前台线程。

  通过将某个线程的IsBackground属性设置为true,使其变为后台线程。

  默认情况下,属于托管线程池的线程(即其IsThreadPoolThread属性为true的线程)都是后台线程,通过创建并启动新的Thread对象而生成的线程都是前台线程。

线程的基本操作

  1.启动线程

    启动线程前,首先要创建一个线程。

    创建无参数线程的一般形式为:

    Thread t1 = new Thread(线程名);

    创建带参数线程的一般形式为(传递一个Object类型的参数):

    Thread t2 = new Thread(线程名(Object obj));

    对于不止一个参数的情况,可以先将这些参数封装到一个类中,然后传递该类的实例,在线程中,再通过该实例访问相应的数据,从而达到启动线程时传递多个参数的目的。 创建线程实例后,就可以调用Start方法启动线程了。

    例如:

     t1.Start(); //不带参数

    t2.Start(“myClass”); //带参数

    注意: 调用Start只是告诉系统启动该线程,但是系统并不一定会立即启动它。

  2.终止线程

   两种方法:

    1.事先设置一个布尔字段,在其他线程中通过修改该布尔量的值作为传递给该线程是否需要终止的判断条件,而在该线程中循环判断该布尔值,以确定是否退出线程,这是结束 线程比较好的方法,实际应用中一般使用这种方法。

    2.调用Thread类的Abort方法,该方法的最终效果是强行终止线程。

  3. 暂停线程

  在多线程应用程序中,有时候并不希望某一个线程继续执行,而是希望该线程暂停一段时间,这样,CPU就会将其时间片中剩余的部分让给另一个线程。

   调用Thread类的Sleep方法可以实现这个功能。

  例如: Thread.Sleep(1000); 这条语句的功能是让当前线程暂停1000毫秒。

  注意 Sleep方法是静态方法,暂停的是该语句所在的线程,而不是其他线程

  4.合并线程

  Join方法用于把指定的线程合并到当前线程中,从而使其变为一个单个的线程。

   如果一个线程t1在执行的过程中需要等待另一个线程t2结束后才能继续执行,可以在t1的代码块中调用t2的join方法。

  例如: t2.Join(); 功能:t1在执行到t2.Join()语句后,就处于暂停状态,直到t2结束后才会继续执行。

  为了避免t1一直等待,可以在调用t2的Join方法的时候指定一个暂停时间,例如:t2.Join(100);

  Volatile关键字

  volatile修饰符表示所声明的字段可以被多个并发执行的线程修改。

  如果某个字段声明包含volatile关键字,则该字段将不再被编译器优化。这样可以确保该字段在任何时间呈现的都是最新的值。

  对于由多个线程访问的字段,而且该字段没有用lock语句对访问进行序列化,声明字段时应该使用volatile修饰符。

  volatile修饰符只能包含在类或结构的字段声明中,不能将局部变量声明为volatile

  在布尔型字段的声明中,添加volatile修饰符的方法如下:

  public volatile bool shouldStop;

  volatile修饰符可应用于以下类型:

  (1)引用类型。

  (2)指针类型(在不安全的上下文中)。

  (3)整型,如sbyte、byte、short、ushort、int、uint、char、float和bool。

  (4)具有整数基类型的枚举类型。

  (5)已知为引用类型的泛型类型参数。

  (6)IntPtr和UIntPtr。

  在一个线程中操作另一个线程的控件

  1、使用委托

  

delegate void AppendStringDelegate(string str);
private void AppendString(string str)
{
    if(richTextBox1.InvokeRequired)
    {
        AppendStringDelegate d = AppendString;
        richTextBox1.Invoke(d, str); }
    else
       {  richTextBox1.Text += str; }
}

  2、UI线程

  

 this.Invoke(new MethodInvoker(delegate()
                {
                    UserGird.DataSource = null;
                    UserGird.DataSource = this.listener.clientInfoList;
                }));

  3、多线程调用UI线程

  

 /// <summary>
    /// 多线程调用UI线程
    /// </summary>
    public class ThreadUIInvoke
    {
        public delegate void EventUIInvoke();

        public delegate void EventUIInvoke<T>(T t);

        public delegate void EventUIInvoke<T, T1>(T t, T1 t1);

        public delegate void EventUIInvoke<T, T1, T2>(T t, T1 t1, T2 t2);

        public delegate void EventUIInvoke<T, T1, T2, T3>(T t, T1 t1, T2 t2, T3 t3);

        public delegate void EventUIInvoke<T, T1, T2, T3, T4>(T t, T1 t1, T2 t2, T3 t3, T4 t4);

        public ThreadUIInvoke(Form form, int interval)
        {
            mContainer = new System.ComponentModel.Container();
            mTimer = new Timer(mContainer);
            mTimer.Tick += OnTick;
            form.Disposed += (o, d) =>
            {
                mTimer.Dispose();
                mContainer.Dispose();

            };
            mTimer.Interval = interval;
            mTimer.Enabled = true;

        }

        private System.ComponentModel.Container mContainer;

        private Timer mTimer;

        private Queue<InvokeItem> mItems = new Queue<InvokeItem>(256);

        private Queue<InvokeItem> mWorkItems = new Queue<InvokeItem>(256);

        private void OnTick(object sender, EventArgs e)
        {
            if (mItems.Count > 0)
            {
                lock (mItems)
                {

                    while (mItems.Count > 0)
                    {
                        mWorkItems.Enqueue(mItems.Dequeue());
                    }
                }
                while (mWorkItems.Count > 0)
                {
                    mWorkItems.Dequeue().Invoke();
                }
            }
        }

        private void Add(InvokeItem item)
        {
            lock (mItems)
            {
                mItems.Enqueue(item);
            }
        }

        public void Invoke(EventUIInvoke eui)
        {
            InvokeItem item = new InvokeItem();
            item.Functions = eui;
            item.Parameters = new object[0];
            Add(item);
        }

        public void Invoke<T>(EventUIInvoke<T> eui, T t)
        {
            InvokeItem item = new InvokeItem();
            item.Functions = eui;
            item.Parameters = new object[] { t };
            Add(item);
        }

        public void Invoke<T, T1>(EventUIInvoke<T, T1> eui, T t, T1 t1)
        {
            InvokeItem item = new InvokeItem();
            item.Functions = eui;
            item.Parameters = new object[] { t, t1 };
            Add(item);
        }

        public void Invoke<T, T1, T2>(EventUIInvoke<T, T1, T2> eui, T t, T1 t1, T2 t2)
        {
            InvokeItem item = new InvokeItem();
            item.Functions = eui;
            item.Parameters = new object[] { t, t1, t2 };
            Add(item);
        }

        public void Invoke<T, T1, T2, T3>(EventUIInvoke<T, T1, T2, T3> eui, T t, T1 t1, T2 t2, T3 t3)
        {
            InvokeItem item = new InvokeItem();
            item.Functions = eui;
            item.Parameters = new object[] { t, t1, t2, t3 };
            Add(item);
        }

        public void Invoke<T, T1, T2, T3, T4>(EventUIInvoke<T, T1, T2, T3, T4> eui, T t, T1 t1, T2 t2, T3 t3, T4 t4)
        {
            InvokeItem item = new InvokeItem();
            item.Functions = eui;
            item.Parameters = new object[] { t, t1, t2, t3, t4 };
            Add(item);
        }

        class InvokeItem
        {
            public Delegate Functions { get; set; }
            public object[] Parameters { get; set; }
            public void Invoke()
            {
                Functions.DynamicInvoke(Parameters);
            }
        }
    }
View Code

  线程同步与线程池

  1、线程的优先级

  五个优先级,由高到低分别是:Highest、AboveNormal、Normal(默认)、BelowNormal和Lowest

  可以使用下面的方法为其赋予较高的优先级:

     Thread t = new Thread(MethodName);

     t.priority = ThreadPriority.AboveNormal;

  通过设置线程的优先级可以改变线程的执行顺序,所设置的优先级仅仅适用于这些线程所属的进程。
  注意:

    当把某线程的优先级设置为Highest时,系统正在运行的其他线程都会终止,所以使用这个优先级别时要特别小心。

  2、同步

  指多个线程之间存在先后执行顺序的关联关系。

  哪些资源需要同步

    系统资源(如通信端口)

    多个进程所共享的资源(如文件句柄)

    由多个线程访问的单个应用程序域的资源(如全局、静态和实例字段)

  为什么要同步

    当两个线程t1和t2有相同的优先级,并且同时在系统上运行时,如果先把时间片分给t1使用,它在结构s1中(该结构中有多个变量)写入某些值,但如果在时间片用完时它仍没有完成写入,这时由于时间片已经分给t2使用,而t2又恰好要尝试读取该结构的值,此时读出的就不是正确的值。这种情况下,如果使用同步仅允许一个线程使用s1,在该线程完成对s1的写入工作后再让t2读取这个结构值,就可以避免出现此类错误。

  解决方法:

    System.Threading命名空间提供了多个用于同步线程的类这些类包括

    Mutex、Monitor、Interlocked和AutoResetEvent。

    在实际应用中经常使用lock语句完成线程同步。 该语句简化了编程的复杂性,使程序看起来既清晰又简洁。

  lock语句的功能

     lock语句可以有效地实现同步,即将代码段(语句块)标记为临界区。它能确保当一个线程位于代码的临界区(可以理解为一段代码)时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码段,则它将一直等待(即被阻塞),直到锁定的对象被释放以后才能进入临界区。

    lock语句的用法 首先利用lock语句锁定某一个对象,然后执行代码段中的语句,等代码段中的语句执行完毕后,再释放该对象。

    

private Object obj = new Object();

    …… lock(obj) { //临界区中的代码 }

  使用lock语句应注意的问题: 锁定的对象名(上面代码中的obj)一般声明为Object类型,不要将其声明为值类型。 锁定的对象名不能将其声明为public,只能为private 临界区中的代码一般不宜太多。

  3、线程池

    线程:

    (1)无限制的创建线程消耗系统资源

    (2)创建线程、回收线程均需要时间

     线程池:是在后台执行多个任务的线程集合。

    (1)最大线程数限制。如果所有线程都繁忙,则额外的任务将放入等待队列中,直到有线程可用时才能够得到处理。

    (2)最小线程数=创建线程池时应立即启动的数目

    (3)一旦池中的某个线程完成任务,它将返回到等待线程队列中,等待被再次使用。这种重用使应用程序可以避免为每个任务创建新线程引起的资源和时间消耗。

    (4)一项工作任务被加入到线程池的队列中,就不能取消该任务,直到该任务完成。

  线程池的默认大小:每个可用处理器25个线程

    每个进程都有一个线程池。 为什么要用线程池? 降低系统开销 可以重用资源,使应用程序可以避免为每个任务创建新线程引起的资源和时间消耗。

    什么情况下才使用线程池? 后台执行,而且不同线程没有优先级区别 适用于需要多个线程而实际执行时间又不多的场合 没有导致线程长时间被阻塞的任务(对于可能长时间被阻塞的任务,应该创建单独的线程处理,不应该使用线程池),这是因为线程池具有最大线程数限制,大量阻塞的线程池线程可能会阻止任务启动。

  ThreadPool

  ThreadPool类位于System.Threading命名空间下。

  ThreadPool提供了对线程池的操作(静态方法),

  例如:发送工作项、处理异步I/O、设置线程数目等

  ThreadPool是一个静态类 托管线程池中的线程为后台线程,即它们的IsBackground属性为true。

  这意味着在所有的前台线程都已退出后,ThreadPool线程也会自动退出。

  GetAvailableThreads方法:检索由GetMaxThreads返回的线程池线程的最大数目和当前活动数目之间的差值

  GetMaxThreads方法:检索可以同时处于活动状态的线程池请求的数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用

  GetMinThreads方法:检索线程池在新请求预测中维护的空闲线程数

  SetMaxThreads方法:可以同时处于活动状态的线程池的请求数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。

  SetMinThreads方法:设置线程池在新请求预测中维护的空闲线程数

  QueueUserWorkItem方法功能

  功能:请求线程池处理一个任务或者工作项 运行时线程池会自动为每一个任务创建线程并且在任务释放时释放线程。

  语法:带一个WaitCallback委托的参数,这个参数包装了要完成的任务

  

Public static bool QueueUserWorkItem ( WaitCallback callBack, object state)

  Public static bool QueueUserWorkItem ( WaitCallback callBack)

  参数: state:包含方法所用数据的对象 callBack: System.Threading.WaitCallback,它表示要执行的方法
  

例如:


  ThreadPool.QueueUserWorkItem(new WaitCallback(方法名),Object参数);

  ThreadPool.QueueUserWorkItem(new WaitCallback(方法名));

  BackgroundWorker组件

  功能:提供了在后台执行耗时操作的功能,利用它既能在后台线程中与界面交互,又能有效地处理资源争用等并发冲突。

  创建方式: 通过编程方式创建BackgroundWorker 从【工具箱】的“组件”选项卡中拖到窗体上。如果在Windows窗体设计BackgroundWorker,则它会出现在组件栏中,而且它的属性会显示在【属性】窗口中。

  常用属性、方法和事件

  CancellationPending属性:指示是否已请求取消后台操作

  IsBusy属性:指示是否正在进行异步操作

  WorkerReportsProgress属性:指示能否报告进度更新

  WorkerSupportsCancellation属性:是否支持异步取消

  CancelAsync方法:请求取消挂起的后台操作

  ReportProgress方法:引发ProgressChanged事件

  RunWorkerAsync方法:执行后台操作 DoWork事件:调用RunWorkerAsync时发生

  ProgressChanged事件:调用ReportProgress时发生 RunWorkerCompleted事件:后台操作已完成、被取消或引发异常时发生

  用法:
­  在DoWork事件中调用耗时的处理程序(线程处理程序)。
 
­  调用RunWorkerAsync方法启动线程(触发事件)。
 
­  在ProgressChanged事件中显示操作信息(相当于操作另一个线程的控件)
 
­  在RunWorkerCompleted事件中处理操作完成时显示的内容