本篇内容介绍了“C#线程的创建和生命周期实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1,获取当前线程信息

Thread.CurrentThread是一个 静态的 Thread 类,Thread 的CurrentThread属性,可以获取到当前运行线程的一些信息,其定义如下:

publicstaticSystem.Threading.ThreadCurrentThread{get;}

Thread 类有很多属性和方法,这里就不列举了,后面的学习会慢慢熟悉更多 API 和深入了解使用。

这里有一个简单的示例:

staticvoidMain(string[]args){Threadthread=newThread(OneTest);thread.Name="Test";thread.Start();Console.ReadKey();}publicstaticvoidOneTest(){ThreadthisTHread=Thread.CurrentThread;Console.WriteLine("线程标识:"+thisTHread.Name);Console.WriteLine("当前地域:"+thisTHread.CurrentCulture.Name);//当前地域Console.WriteLine("线程执行状态:"+thisTHread.IsAlive);Console.WriteLine("是否为后台线程:"+thisTHread.IsBackground);Console.WriteLine("是否为线程池线程"+thisTHread.IsThreadPoolThread);}

输出

线程标识:Test当前地域:zh-CN线程执行状态:True是否为后台线程:False是否为线程池线程False2,管理线程状态

一般认为,线程有五种状态:

新建(new 对象) 、就绪(等待CPU调度)、运行(CPU正在运行)、阻塞(等待阻塞、同步阻塞等)、死亡(对象释放)。

理论的东西不说太多,直接撸代码。

2.1 启动与参数传递

新建线程简直滚瓜烂熟,无非new一下,然后Start()

Threadthread=newThread();

Thread 的构造函数有四个:

publicThread(ParameterizedThreadStartstart);publicThread(ThreadStartstart);publicThread(ParameterizedThreadStartstart,intmaxStackSize);publicThread(ThreadStartstart,intmaxStackSize);

我们以启动新的线程时传递参数来举例,使用这四个构造函数呢?

2.1.1 ParameterizedThreadStart

ParameterizedThreadStart 是一个委托,构造函数传递的参数为需要执行的方法,然后在Start方法中传递参数。

需要注意的是,传递的参数类型为 object,而且只能传递一个。

代码示例如下:

staticvoidMain(string[]args){stringmyParam="abcdef";ParameterizedThreadStartparameterized=newParameterizedThreadStart(OneTest);Threadthread=newThread(parameterized);thread.Start(myParam);Console.ReadKey();}publicstaticvoidOneTest(objectobj){stringstr=objasstring;if(string.IsNullOrEmpty(str))return;Console.WriteLine("新的线程已经启动");Console.WriteLine(str);}2.1.2 使用静态变量或类成员变量

此种方法不需要作为参数传递,各个线程共享堆栈。

优点是不需要装箱拆箱,多线程可以共享空间;缺点是变量是大家都可以访问,此种方式在多线程竞价时,可能会导致多种问题(可以加锁解决)。

下面使用两个变量实现数据传递:

classProgram{privatestringA="成员变量";publicstaticstringB="静态变量";staticvoidMain(string[]args){//创建一个类Programp=newProgram();Threadthread1=newThread(p.OneTest1);thread1.Name="Test1";thread1.Start();Threadthread2=newThread(OneTest2);thread2.Name="Test2";thread2.Start();Console.ReadKey();}publicvoidOneTest1(){Console.WriteLine("新的线程已经启动");Console.WriteLine(A);//本身对象的其它成员}publicstaticvoidOneTest2(){Console.WriteLine("新的线程已经启动");Console.WriteLine(B);//全局静态变量}}2.1.3 委托与Lambda

原理是 Thread 的构造函数public Thread(ThreadStart start);ThreadStart是一个委托,其定义如下

publicdelegatevoidThreadStart();

使用委托的话,可以这样写

staticvoidMain(string[]args){System.Threading.ThreadStartstart=DelegateThread;Threadthread=newThread(start);thread.Name="Test";thread.Start();Console.ReadKey();}publicstaticvoidDelegateThread(){OneTest("a","b",666,newProgram());}publicstaticvoidOneTest(stringa,stringb,intc,Programp){Console.WriteLine("新的线程已经启动");}

有那么一点点麻烦,不过我们可以使用 Lambda 快速实现。

使用 Lambda 示例如下:

staticvoidMain(string[]args){Threadthread=newThread(()=>{OneTest("a","b",666,newProgram());});thread.Name="Test";thread.Start();Console.ReadKey();}publicstaticvoidOneTest(stringa,stringb,intc,Programp){Console.WriteLine("新的线程已经启动");}

提示:如果需要处理的算法比较简单的话,可以直接写进委托中,不需要另外写方法啦。

可以看到,C# 是多么的方便。

2.2 暂停与阻塞

Thread.Sleep()方法可以将当前线程挂起一段时间,Thread.Join()方法可以阻塞当前线程一直等待另一个线程运行至结束。

在等待线程Sleep()Join()的过程中,线程是阻塞的(Blocket)。

阻塞的定义:当线程由于特点原因暂停执行,那么它就是阻塞的。
如果线程处于阻塞状态,线程就会交出他的 CPU 时间片,并且不会消耗 CPU 时间,直至阻塞结束。
阻塞会发生上下文切换。

代码示例如下:

staticvoidMain(string[]args){Threadthread=newThread(OneTest);thread.Name="小弟弟";Console.WriteLine($"{DateTime.Now}:大家在吃饭,吃完饭后要带小弟弟逛街");Console.WriteLine("吃完饭了");Console.WriteLine($"{DateTime.Now}:小弟弟开始玩游戏");thread.Start();//化妆5sConsole.WriteLine("不管他,大姐姐化妆先");Thread.Sleep(TimeSpan.FromSeconds(5));Console.WriteLine($"{DateTime.Now}:化完妆,等小弟弟打完游戏");thread.Join();Console.WriteLine("打完游戏了嘛?"+(!thread.IsAlive?"true":"false"));Console.WriteLine($"{DateTime.Now}:走,逛街去");Console.ReadKey();}publicstaticvoidOneTest(){Console.WriteLine(Thread.CurrentThread.Name+"开始打游戏");for(inti=0;i<10;i++){Console.WriteLine($"{DateTime.Now}:第几局:"+i);Thread.Sleep(TimeSpan.FromSeconds(2));//休眠2秒}Console.WriteLine(Thread.CurrentThread.Name+"打完了");}

Join() 也可以实现简单的线程同步,即一个线程等待另一个线程完成。

2.3 线程状态

ThreadState是一个枚举,记录了线程的状态,我们可以从中判断线程的生命周期和健康情况。

其枚举如下:

枚举值说明Initialized0此状态指示线程已初始化但尚未启动。Ready1此状态指示线程因无可用的处理器而等待使用处理器。 线程准备在下一个可用的处理器上运行。Running2此状态指示线程当前正在使用处理器。Standby3此状态指示线程将要使用处理器。 一次只能有一个线程处于此状态。Terminated4此状态指示线程已完成执行并已退出。Transition6此状态指示线程在可以执行前等待处理器之外的资源。 例如,它可能正在等待其执行堆栈从磁盘中分页。Unknown7线程的状态未知。Wait5此状态指示线程尚未准备好使用处理器,因为它正在等待外围操作完成或等待资源释放。 当线程就绪后,将对其进行重排。

但是里面有很多枚举类型是没有用处的,我们可以使用一个这样的方法来获取更加有用的信息:

publicstaticThreadStateGetThreadState(ThreadStatets){returnts&(ThreadState.Unstarted|ThreadState.WaitSleepJoin|ThreadState.Stopped);}

根据2.2中的示例,我们修改一下 Main 中的方法:

staticvoidMain(string[]args){Threadthread=newThread(OneTest);thread.Name="小弟弟";Console.WriteLine($"{DateTime.Now}:大家在吃饭,吃完饭后要带小弟弟逛街");Console.WriteLine("吃完饭了");Console.WriteLine($"{DateTime.Now}:小弟弟开始玩游戏");Console.WriteLine("弟弟在干嘛?(线程状态):"+Enum.GetName(typeof(ThreadState),GetThreadState(thread.ThreadState)));thread.Start();Console.WriteLine("弟弟在干嘛?(线程状态):"+Enum.GetName(typeof(ThreadState),GetThreadState(thread.ThreadState)));//化妆5sConsole.WriteLine("不管他,大姐姐化妆先");Thread.Sleep(TimeSpan.FromSeconds(5));Console.WriteLine("弟弟在干嘛?(线程状态):"+Enum.GetName(typeof(ThreadState),GetThreadState(thread.ThreadState)));Console.WriteLine($"{DateTime.Now}:化完妆,等小弟弟打完游戏");thread.Join();Console.WriteLine("弟弟在干嘛?(线程状态):"+Enum.GetName(typeof(ThreadState),GetThreadState(thread.ThreadState)));Console.WriteLine("打完游戏了嘛?"+(!thread.IsAlive?"true":"false"));Console.WriteLine($"{DateTime.Now}:走,逛街去");Console.ReadKey();}

代码看着比较乱,请复制到项目中运行一下。

输出示例:

2020/4/1111:01:48:大家在吃饭,吃完饭后要带小弟弟逛街吃完饭了2020/4/1111:01:48:小弟弟开始玩游戏弟弟在干嘛?(线程状态):Unstarted弟弟在干嘛?(线程状态):Running不管他,大姐姐化妆先小弟弟开始打游戏2020/4/1111:01:48:第几局:02020/4/1111:01:50:第几局:12020/4/1111:01:52:第几局:2弟弟在干嘛?(线程状态):WaitSleepJoin2020/4/1111:01:53:化完妆,等小弟弟打完游戏2020/4/1111:01:54:第几局:32020/4/1111:01:56:第几局:42020/4/1111:01:58:第几局:52020/4/1111:02:00:第几局:62020/4/1111:02:02:第几局:72020/4/1111:02:04:第几局:82020/4/1111:02:06:第几局:9小弟弟打完了弟弟在干嘛?(线程状态):Stopped打完游戏了嘛?true2020/4/1111:02:08:走,逛街去

可以看到UnstartedWaitSleepJoinRunningStopped四种状态,即未开始(就绪)、阻塞、运行中、死亡。

2.4 终止

.Abort()方法不能在 .NET Core 上使用,不然会出现System.PlatformNotSupportedException:“Thread abort is not supported on this platform.”

后面关于异步的文章会讲解如何实现终止。

由于 .NET Core 不支持,就不理会这两个方法了。这里只列出 API,不做示例。

方法说明Abort()在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。 调用此方法通常会终止线程。Abort(Object)引发在其上调用的线程中的 ThreadAbortException以开始处理终止线程,同时提供有关线程终止的异常信息。 调用此方法通常会终止线程。

Abort()方法给线程注入ThreadAbortException异常,导致程序被终止。但是不一定可以终止线程。

2.5 线程的不确定性

线程的不确定性是指几个并行运行的线程,不确定在下一刻 CPU 时间片会分配给谁(当然,分配有优先级)。

对我们来说,多线程是同时运行的,但一般 CPU 没有那么多核,不可能在同一时刻执行所有的线程。CPU 会决定某个时刻将时间片分配给多个线程中的一个线程,这就出现了 CPU 的时间片分配调度。

执行下面的代码示例,你可以看到,两个线程打印的顺序是不确定的,而且每次运行结果都不同。

CPU 有一套公式确定下一次时间片分配给谁,但是比较复杂,需要学习计算机组成原理和操作系统。

留着下次写文章再讲。

staticvoidMain(string[]args){Threadthread1=newThread(Test1);Threadthread2=newThread(Test2);thread1.Start();thread2.Start();Console.ReadKey();}publicstaticvoidTest1(){for(inti=0;i<10;i++){Console.WriteLine("Test1:"+i);}}publicstaticvoidTest2(){for(inti=0;i<10;i++){Console.WriteLine("Test2:"+i);}}2.6 线程优先级、前台线程和后台线程

Thread.Priority属性用于设置线程的优先级,Priority是一个 ThreadPriority 枚举,其枚举类型如下

枚举值说明AboveNormal3可以将 安排在具有Highest优先级的线程之后,在具有Normal优先级的线程之前。BelowNormal1可以将 Thread 安排在具有Normal优先级的线程之后,在具有Lowest优先级的线程之前。Highest4可以将 Thread 安排在具有任何其他优先级的线程之前。Lowest0可以将 Thread 安排在具有任何其他优先级的线程之后。Normal2可以将 Thread 安排在具有AboveNormal优先级的线程之后,在具有BelowNormal优先级的线程之前。 默认情况下,线程具有Normal优先级。

优先级排序:Highest>AboveNormal>Normal>BelowNormal>Lowest

Thread.IsBackgroundThread可以设置线程是否为后台线程。

前台线程的优先级大于后台线程,并且程序需要等待所有前台线程执行完毕后才能关闭;而当程序关闭是,无论后台线程是否在执行,都会强制退出。

2.7 自旋和休眠

当线程处于进入休眠状态或解除休眠状态时,会发生上下文切换,这就带来了昂贵的消耗。

而线程不断运行,就会消耗 CPU 时间,占用 CPU 资源。

对于过短的等待,应该使用自旋(spin)方法,避免发生上下文切换;过长的等待应该使线程休眠,避免占用大量 CPU 时间。

我们可以使用最为熟知的Sleep()方法休眠线程。有很多同步线程的类型,也使用了休眠手段等待线程(已经写好草稿啦)。

自旋的意思是,没事找事做。

例如:

publicstaticvoidTest(intn){intnum=0;for(inti=0;i<n;i++){num+=1;}}

通过做一些简单的运算,来消耗时间,从而达到等待的目的。

C# 中有关于自旋的自旋锁和Thread.SpinWait();方法,在后面的线程同步分类中会说到自旋锁。

Thread.SpinWait()在极少数情况下,避免线程使用上下文切换很有用。其定义如下

publicstaticvoidSpinWait(intiterations);

SpinWait 实质上是(处理器)使用了非常紧密的循环,并使用iterations参数指定的循环计数。 SpinWait 等待时间取决于处理器的速度。

SpinWait 无法使你准确控制等待时间,主要是使用一些锁时用到,例如 Monitor.Enter。

“C#线程的创建和生命周期实例分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!