.net多线程的使用(Thread)
- 格式:pdf
- 大小:422.67 KB
- 文档页数:7
.net多线程的使⽤(Thread)
上篇
中篇 多线程的使⽤(Thread)
下篇 net 任务⼯⼚实现异步多线程
Thread多线程概述
上⼀篇我们介绍了net 的同步与异步,我们异步演⽰的时候使⽤的是委托多线程来实现的。今天我们来细细的剖析下 多线程。
多线程的优点:可以同时完成多个任务;可以使程序的响应速度更快;可以让占⽤⼤量处理时间的任务或当前没有进⾏处理的任务定期将处理时间让给别的任务;可以随时停⽌
任务;可以设置每个任务的优先级以优化程序性能。
然⽽,多线程虽然有很多优点,但是也必须认识到多线程可能存在影响系统性能的不利⽅⾯,才能正确使⽤线程。弊端主要有如下⼏点:
(1)线程也是程序,所以线程需要占⽤内存,线程越多,占⽤内存也越多。
(2)多线程需要协调和管理,所以需要占⽤CPU时间以便跟踪线程[时间空间转换,简称时空转换]。
(3)线程之间对共享资源的访问会相互影响,必须解决争⽤共享资源的问题。
(4)线程太多会导致控制太复杂,最终可能造成很多程序缺陷。
当启动⼀个可执⾏程序时,将创建⼀个主线程。在默认的情况下,C#程序具有⼀个线程,此线程执⾏程序中以Main⽅法开始和结束的代码,Main()⽅法直接或间接执⾏的每
⼀个命令都有默认线程(主线程)执⾏,当Main()⽅法返回时此线程也将终⽌。
⼀个进程可以创建⼀个或多个线程以执⾏与该进程关联的部分程序代码。在C#中,线程是使⽤Thread类处理的,该类在System.Threading命名空间中。使⽤Thread类创建线程
时,只需要提供线程⼊⼝,线程⼊⼝告诉程序让这个线程做什么。通过实例化⼀个Thread类的对象就可以创建⼀个线程。
多线程Thread
上⼀节我们通过了线程的异步了解了线程的等待和线程回调的区别、学习了不卡主线程的原理。这⾥我们简单学习下 Thread 类的使⽤.我们分别让主线程和⼦线程循环输出 1-
100,我们来看下结果。
Thread类接收⼀个ThreadStart委托或ParameterizedThreadStart委托的构造函数,该委托包装了调⽤Start⽅法时由新线程调⽤的⽅法,⽰例代码如下:
Thread thread=new Thread(new ThreadStart(method));//创建线程
thread.Start(); //启动线程
1.线程的⽆序性质
Thread t = new Thread(()=> {
for (int i = 0; i < 100; i++)
{
Console.WriteLine($"我是⼦线程({i})~~~~");
}
});// 参数 ThreadStart 是⼀个委托类型的。凡是委托,我们都可以使⽤lambda 表达式代替
t.Start();//Start 通过Start开启⼀个线程
for (int i = 0; i < 100; i++)
{
Console.WriteLine($"我是主线程【{i}】");
}
输出结果:
通过执⾏结果我们会看到,主线程和⼦线程不是⼀味的执⾏,是兼续的。也就是说主线程和⼦线程在执⾏过程中是互相抢CPU资源进⾏计算的。
这⾥我们可以总结下, 异步多线程三⼤特点:
(1) 同步卡界⾯,UI线程被占⽤;异步多线程不卡界⾯,UI线程空闲,计算任务交给⼦线程。(2) 同步⽅法慢,因为只有⼀个线程⼲活;异步多线程⽅法快,因为多个线程并发计算(空间换时间), 这⾥也会消耗更多的资源,不是线程的线性关系(倍数关系),不是线
程越多越好(1资源有限 2线程调度耗资源 3不稳定)
(3) ※异步多线程是⽆序的,不可预测的:启动顺序不确定、消耗时间不确定、结束顺序不确定( 这⾥特别提醒下,我们不要试图控制执⾏的顺序)
异步与多线程的区别: 异步是主线程⽴即启动,启动完成以后,在执⾏⼦线程。但是多线程不⼀定是谁先启动。如果看不懂,请看本⼈。
关于线程的⽆续,这⾥就不过多的解释了继续往下看。
2.前台线程与后台线程
在上⼀篇⽂章我们简单的说到了,线程本⾝并不是任何⾼级语⾔的概念,本⾝是计算机的概念,只是⾼级语⾔给予封装了⼀层。
前台线程:窗体Ui主线程退出(销毁)以后,⼦线程必须计算完成才能退出。请下载demo ⾃⾏查看,还是上⾯的案列。
后台线程:窗体Ui主线程退出(销毁)以后,⼦线程就会退出。关闭窗体以后,控制台就退出了。我们通过Thread 类的 IsBackground属性进⾏设置;设置为true 的时候为后台
线程,默认为false前台线程。看下边案列
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(()=> {
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);//演⽰前台线程和后台线程,演⽰线程⽆序性质,请注释掉
Console.WriteLine($"我是⼦线程({i})~~~~");
}
});// 参数 ThreadStart 是⼀个委托类型的。凡是委托,我们都可以使⽤lambda 表达式代替
t.IsBackground=true;//设置为true 的时候为后台线程,默认为false前台线程。关闭窗体会⽴即停⽌计算。
t.Start();//Start 通过Start开启⼀个线程
for (int i = 0; i < 100; i++)
{
Console.WriteLine($"我是主线程【{i}】");
}
}后台线程
后台线程⼀般⽤于处理不重要的事情,应⽤程序结束时,后台线程是否执⾏完成对整个应⽤程序没有影响。如果要执⾏的事情很重要,需要将线程设置为前台线程。
3.线程的状态及属性⽅法的使⽤
这⾥简单列下线程的常⽤属性
属性名称说明
CurrentContext获取线程正在其中执⾏的当前上下⽂。
CurrentThread获取当前正在运⾏的线程。
ExecutionContext获取⼀个 ExecutionContext 对象,该对象包含有关当前线程的各种上下⽂的信息。
IsAlive获取⼀个值,该值指⽰当前线程的执⾏状态。
IsBackground获取或设置⼀个值,该值指⽰某个线程是否为后台线程。
IsThreadPoolThread获取⼀个值,该值指⽰线程是否属于托管线程池。
ManagedThreadId获取当前托管线程的唯⼀标识符。
Name获取或设置线程的名称。
Priority获取或设置⼀个值,该值指⽰线程的调度优先级。
ThreadState获取⼀个值,该值包含当前线程的状态。
通过ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它⽐ IsAlive 属性能提供更多的特定信息。
前⾯说过,⼀个应⽤程序域中可能包括多个上下⽂,⽽通过CurrentContext可以获取线程当前的上下⽂。
CurrentThread是最常⽤的⼀个属性,它是⽤于获取当前运⾏的线程。
Thread.CurrentThread.ManagedThreadId 获取线程的ID ,我们尽量不要使⽤ Thread.CurrentThread.Name,因为name 并不是唯⼀的
线程状态值Thread 中包括了多个⽅法来控制线程的创建、挂起、停⽌、销毁,以后来的例⼦中会经常使⽤。
⽅法名称说明
Abort() 终⽌本线程。
GetDomain()返回当前线程正在其中运⾏的当前域。
GetDomainId()返回当前线程正在其中运⾏的当前域Id。
Interrupt()中断处于 WaitSleepJoin 线程状态的线程。
Join()已重载。 阻塞调⽤线程,直到某个线程终⽌时为⽌。
Resume()继续运⾏已挂起的线程。
Start() 执⾏本线程。
Suspend()挂起当前线程,如果当前线程已属于挂起状态则此不起作
⽤
Sleep() 把正在运⾏的线程挂起⼀段时间。
关于线程状态操作的⽅法,我们尽量不要使⽤,这⾥以销毁线程的⽅法为列⼦去演⽰。本图表除了第⼀个以外,都可以使⽤。
注意:
在我们net 中,语⾔分为托管代码和⾮托管代码。托管代码是可以控制的,⾮托管代码是不可控制的.我们线程销毁实际上是抛出了⼀个异常,当我们程序捕获到这个异常以后,
线程因为异常⽽退出。所以销毁线程在某些(值⾮托管调⽤)时候会出现问题的,我们后续在任务⼯⼚ FacTask 的时候,再去详细讲解销毁线程。如果⾮要"停⽌线程 靠的不是
外部⼒量⽽是线程⾃⾝,外部修改信号量,线程检测信号量,当线程检测到信号量以后,我们抛出异常,在主线程中捕获异常即可".
4.线程等待
我们在学习异步的时候,知道UI主线程在等待⼦线程的时候,窗体是不可以移动的。并且我们可以有实时返回(有损耗)的等待(阻塞)和⼀直阻塞主线程(⽆损耗)等待⼦线
程完事,再去执⾏主线程。下⾯我们来看看⼀个案列,案列以5名学⽣写作业为例⼦。
public void WriteJob(string name) {
Console.WriteLine("********************** "+ name + " Start【" + Thread.CurrentThread.ManagedThreadId + "】等待............... ***");
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 10; i++)
{
Thread.Sleep(32);
Console.WriteLine($"学⽣:{name}在做第{i+1}题作业");
}
watch.Stop();
Console.WriteLine("********************** " + name + " End【" + Thread.CurrentThread.ManagedThreadId + "】 ⽤时"+ watch.ElapsedMilliseconds + "毫秒............... ***");
}
///
/// 线程等待
///
///
///
private void button2_Click(object sender, EventArgs e)
{
Console.WriteLine("**********************button2_Click Start【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
List threadList = new List();
for (int i = 0; i < 5; i++)
{
string studentName = "甲" + i + "同学";
Thread thread = new Thread(() => WriteJob(studentName));
Console.WriteLine($"{studentName}开始写作业");