操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。

  在Windows操作系统下,线程的的数据结构包含以下内容:

  1、线程的核心对象:主要包含线程当前的寄存器状态,当操作系统调度这个线程开始运行的时候,寄存器的状态将被加载到CPU中,重新构建线程的执行环境,当线程被调度出来的时候,最后的寄存器状态被重新保存到这里,已备下一次执行的时候使用。
  2、线程环境块(Thread Environment Block,TED):是一块用户模式下的内存,包含线程的异常处理链的头部。另外,线程的局部存储数据(Thread Local Storage Data)也存在这里。
  3、用户模式的堆栈:用户程序的局部变量和参数传递所使用的堆栈,默认情况下,Windows将会被分配1M的空间用于用户模式堆栈。
  4、内核模式堆栈:用于访问操作系统时使用的堆栈。

在抢先式多任务的环境下,在一个特定的时间,CPU将一个线程调度进CPU中执行,这个线程最多将会运行一个时间片的时间长度,当时间片到期之后,操作系统将这个线程调度出CPU,将另外一个线程调度进CPU,我们通常称这种操作为上下文切换。

  在每一次的上下文切换时,Windows将执行下面的步骤:

  • 将当前的CPU寄存器的值保存到当前运行的线程数据结构中,即其中的线程核心对象中。
  • 选中下一个准备运行的线程,如果这个线程处于不同的进程中,那么,还必须首先切换虚拟地址空间。
  • 加载准备运行线程的CPU寄存器状态到CPU中。

  公共语言运行时CLR(Common Language Runtime)是.Net程序运行的环境,它负责资源管理,并保证应用和底层操作系统之间必要的分离。

  在.Net环境下,CLR中的线程需要通过操作系统的线程完成实际的工作,目前情况下,.Net直接将CLR中的线程映射到操作系统的线程进行处理和调度,所以,我们每创建一个线程将会消耗1M以上的内存空间。但未来CLR中的线程并不一定与操作系统中的线程完全对应。通过创建CLR环境下的逻辑线程,我们可能创建更加节省资源的线程,使得大量的CLR线程可以工作在少量的操作系统线程之上。

 

1. System.Threading.Thread类

System.Threading.Thread是用于控制线程的基础类,通过Thread可以控制当前应用程序域中线程的创建、挂起、停止、销毁。

它包括以下常用公共属性:

属性名称说明
CurrentContext 获取线程正在其中执行的当前上下文。
CurrentThread 获取当前正在运行的线程。
ExecutionContext 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive 获取一个值,该值指示当前线程的执行状态。
IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread 获取一个值,该值指示线程是否属于托管线程池。
ManagedThreadId 获取当前托管线程的唯一标识符。
Name 获取或设置线程的名称。
Priority 获取或设置一个值,该值指示线程的调度优先级。
ThreadState 获取一个值,该值包含当前线程的状态。

 常用属性示例:

using System;
using System.Threading;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //新建3个线程并设定各自的优先级
            Thread t1 = new Thread(Run);
            t1.Priority = ThreadPriority.Normal;
            t1.Start();

            Console.ReadKey();
        }

        public static void Run()
        {
            Thread t1 = Thread.CurrentThread;   //静态属性,获取当前执行这行代码的线程
            Console.WriteLine("我的优先级是:" + t1.Priority);
            Console.WriteLine("我是否还在执行:" + t1.IsAlive);
            Console.WriteLine("是否是后台线程:" + t1.IsBackground);
            Console.WriteLine("是否是线程池线程:" + t1.IsThreadPoolThread);
            Console.WriteLine("线程唯一标识符:" + t1.ManagedThreadId);
            Console.WriteLine("我的名称是:" + t1.Name);
            Console.WriteLine("我的状态是:" + t1.ThreadState);
        }
    }
}
View Code

 

2. 线程的标识符

ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。而Name是一个可变值,在默认时候,Name为一个空值 Null,开发人员可以通过程序设置线程的名称,但这只是一个辅助功能。

 

3. 线程的优先级别

.NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。

成员名称说明
Lowest 可以将 Thread 安排在具有任何其他优先级的线程之后。
BelowNormal 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
Normal 默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有BelowNormal 优先级的线程之前。
AboveNormal 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
Highest 可以将 Thread 安排在具有任何其他优先级的线程之前。

 优先级的示例:

using System;
using System.Threading;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //新建3个线程并设定各自的优先级
            Thread t1 = new Thread(Run);
            t1.Priority = ThreadPriority.Lowest;
            Thread t2 = new Thread(Run);
            t2.Priority = ThreadPriority.Normal;
            Thread t3 = new Thread(Run);
            t3.Priority = ThreadPriority.Highest;
            //由低到高优先级的顺序依次调用
            t1.Start();
            t2.Start();
            t3.Start();
            Console.ReadKey();
        }

        public static void Run()
        {
            Console.WriteLine("我的优先级是:" + Thread.CurrentThread.Priority);
        }
    }
}
View Code

4. 线程的状态

通过ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。

前面说过,一个应用程序域中可能包括多个上下文,而通过CurrentContext可以获取线程当前的上下文。

CurrentThread是最常用的一个属性,它是用于获取当前运行的线程。

 

5. System.Threading.Thread的方法

Thread 中包括了多个方法来控制线程的创建、挂起、停止、销毁,以后来的例子中会经常使用。

方法名称说明
Abort()     终止本线程。
GetDomain() 返回当前线程正在其中运行的当前域。
GetDomainId() 返回当前线程正在其中运行的当前域Id。
Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。
Join() 已重载。 阻塞调用线程,直到某个线程终止时为止。
Resume() 继续运行已挂起的线程。
Start()   执行本线程。
Suspend() 挂起当前线程,如果当前线程已属于挂起状态则此不起作用
Sleep()   把正在运行的线程挂起一段时间。

6. 开发实例

 前台线程与后台线程的区别

  我们看到上面有个属性叫后台线程,非后台线程就叫前台线程吧,Thread.Start()启动的线程默认为前台线程,启动程序时创建的主线程一定是前台线程。应用程序与必须等到所有的前台线程执行完毕才会卸载。而当IsBackground设置为true时,就是后台线程了,当主线程执行完毕后就直接卸载,不再理会后台线程是否执行完毕。

  前台与后台线程的设置必须在线程启动之前进行设置,线程启动之后就不能设置了。

Thread创建的线程是前台线程,线程池中的是后台线程。

1、启动线程(.Start)
  1.1、无参数的调用
   Thread t = new Thread(Action);
   t.Start();
  1.2、带参数的调用方式一
   Thread t = new Thread(Action<>);
   t.Start(参数);
  1.3、带参数的调用方式二
   Thread t = new Thread(() => Action<>(参数));
   t.Start();
  1.4、如果遇到需要返回值,就可以考虑用异步;
   public delegate string MethodCaller(string name);//定义个代理 
   MethodCaller mc = new MethodCaller(GetName); 
   string name = "my name";//输入参数 
   IAsyncResult result = mc.BeginInvoke(name,null, null); 
   string myname = mc.EndInvoke(result);//用于接收返回值 
 
   public string GetName(string name)    // 函数
   {
      return name;
   }
using System;
using System.Threading;

namespace ConsoleApp1
{
    public delegate string MethodCaller(string name);//定义个代理 

    class Program
    {
        static void CountNumbers()
        {
            int _iterations = 3;
            for (int i = 1; i <= _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
            }
        }

        static void Count(object iterations)
        {
            CountNumbers((int)iterations);
        }

        static void CountNumbers(int iterations)
        {
            for (int i = 1; i <= iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
            }
        }

        static void PrintNumber(int number)
        {
            Console.WriteLine(number);
        }

        static string GetName(string name)    // 函数
        {
            int _iterations = 5;
            for (int i = 1; i <= _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
            }
            return name;
        }

        static void Main(string[] args)
        {
            //无参数的调用
            var threadOne = new Thread(CountNumbers);
            threadOne.Name = "ThreadOne";
            threadOne.Start();
            threadOne.Join();
            Console.WriteLine("--------------------------");

            //带参数的调用方式一
            var threadTwo = new Thread(Count);
            threadTwo.Name = "ThreadTwo";
            threadTwo.Start(3);
            threadTwo.Join();
            Console.WriteLine("--------------------------");

            //带参数的调用方式二
            var threadThree = new Thread(() => CountNumbers(4));
            threadThree.Name = "ThreadThree";
            threadThree.Start();
            threadThree.Join();
            Console.WriteLine("--------------------------");

            //注意在多个lambda表达式中使用香港的变量,它们会共享该变量 这里两个线程的参数都是20
            int i = 10;
            var threadFour = new Thread(() => PrintNumber(i));
            i = 20;
            var threadFive = new Thread(() => PrintNumber(i));
            threadFour.Start();
            threadFive.Start();
            Console.WriteLine("--------------------------");

            MethodCaller mc = new MethodCaller(GetName);
            string name = "my name";//输入参数 
            IAsyncResult result = mc.BeginInvoke(name, null, null);
            Console.WriteLine("继续主线程的业务");
            string myname = mc.EndInvoke(result);//用于接收返回值 
            Console.WriteLine(string.Format("result={0}", myname));
        }
    }
}
View Code

2、等待线程结束,会阻塞当前主线程(.Join)

using System;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting program...");
            Thread t = new Thread(PrintNumbersWithDelay);
            t.Start();
            t.Join();
            Console.WriteLine("Thread completed");
        }

        static void PrintNumbersWithDelay()
        {
            Console.WriteLine("Starting...");
            for (int i = 1; i <= 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                Console.WriteLine(i);
            }
        }
    }
}
View Code

3、线程中异常的处理只能在线程调用中的函数里面去处理,在外面是接收不到线程中的报错的

using System;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var t = new Thread(FaultyThread);
            t.Start();
            t.Join();

            //try
            //{
            //    t = new Thread(BadFaultyThread);
            //    t.Start();
            //}
            //catch (Exception ex)
            //{
            //    //这里捕获不了线程中的异常
            //    Console.WriteLine("We won't get here!");
            //}
        }

        static void BadFaultyThread()
        {
            Console.WriteLine("Starting a Badfaulty thread...");
            Thread.Sleep(TimeSpan.FromSeconds(2));
            throw new Exception("Boom!");
        }

        static void FaultyThread()
        {
            try
            {
                Console.WriteLine("Starting a faulty thread...");
                Thread.Sleep(TimeSpan.FromSeconds(1));
                throw new Exception("Boom!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception handled: {0}", ex.Message);
            }
        }
    }
}
View Code

4、设置线程优先级(.Priority)

using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Current thread priority: {0}", Thread.CurrentThread.Priority);
            Console.WriteLine("Running on all cores available");
            RunThreads();
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine("Running on a single core");
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
            RunThreads();
            Console.ReadLine();
        }

        static void RunThreads()
        {
            var sample = new ThreadSample();

            var threadOne = new Thread(sample.CountNumbers);
            threadOne.Name = "ThreadOne";
            var threadTwo = new Thread(sample.CountNumbers);
            threadTwo.Name = "ThreadTwo";

            threadOne.Priority = ThreadPriority.Lowest;
            threadTwo.Priority = ThreadPriority.Highest;
            threadOne.Start();
            threadTwo.Start();

            Thread.Sleep(TimeSpan.FromSeconds(2));
            sample.Stop();
        }

        class ThreadSample
        {
            private bool _isStopped = false;

            public void Stop()
            {
                _isStopped = true;
            }

            public void CountNumbers()
            {
                long counter = 0;

                while (!_isStopped)
                {
                    counter++;
                }

                Console.WriteLine("{0} with {1,11} priority " +
                            "has a count = {2,13}", Thread.CurrentThread.Name,
                            Thread.CurrentThread.Priority,
                            counter.ToString("N0"));
            }
        }
    }
}
View Code