这节讲一下多线程(Thread)技术。
在讲线程之前,先区分一下程序,进程,线程三者的区别,大体上说,一个程序可以分为多个进程,一个进程至少由一个线程去执行,它们是层层包含的关系。我们写的程序,就是一个进程,多个进程,以完成一个用户服务,或者完成一个大的界面展现,就组成一个程序,但在CPU层面,只有线程的概念,线程是最小的执行单位,Windows中采用CPU轮换制度,CPU给每个要执行的线程分配操作时间,轮流执行,但因为CPU的主频实在是太高,我们感受不到每个程序轮空期卡顿。
一个进程,开了一条线程去执行,那么这个线程就是主线程,一般在UI程序中,如果主线程执行CPU密集型的耗时工作(如IO操作),那么就会导致界面处于”假死“状态,直到主线程完成这个耗时的任务,所以,我们需要解决这种假死的问题,以带给用户更好的交互体验,那么就要用到多线程技术,将耗时的工作,交给后台线程执行。
创建线程
使用Thread类创建线程,该类位于System.Thread类之下,必须在创建之时给填入执行方法,或者填入lambda表达式。
代码语言:javascript复制Thread thread=new Thread(() =>
{
for (int i = 0; i <= 100; i )
{
Console.WriteLine($"{Thread.CurrentThread.Name}=========>{i}");
}
});
以上是创建了一个线程,并填入一个lambda表达式,输出当前线程的名称和0-100,注意,创建并不是启动,启动需要调用Start()方法。
下面看一段完整的代码:
代码语言:javascript复制Thread thread=new Thread(() =>
{
Console.WriteLine(Thread.CurrentThread.IsAlive);
for (int i = 0; i <= 100; i )
{
Console.WriteLine($"{Thread.CurrentThread.Name}=========>{i}");
}
});
thread.Name = "BackgroundThread";
thread.IsBackground = true;
thread.Start();
for (int i = 0; i <= 100; i )
{
Console.WriteLine($"main=========>{i}");
}
Console.WriteLine(thread.ManagedThreadId);
Thread.Sleep(200);
Console.WriteLine(thread.IsAlive);
先介绍一下代码中出现的几个属性和方法:
Name属性,故名思意,这是为线程起一个名字,
IsBackground属性,设置线程是否是后台线程,如果前台线程也就是主线程结束运行,它所有的后台线程也会立即终止。
IsAlive只读bool属性,标识当前线程是否执行完毕,这里要说一下线程的生命周期,它的声明周期是从开始执行到结束,执行完交给线程的代码线程立即dead掉,也可以强行挂起线程,但这个方法已被舍弃,因为强行挂起线程有弊端,就像在高速上跑的小汽车,不能随便就直接拦截。
ManagedThreadId属性,获取当前线程ID
Thread.CurrentThread属性,获取当前的线程。
Thread.Sleep(毫秒值)方法,执行到此方法,线程会睡眠,哪个线程执行,哪个线程睡,此处让其睡200毫秒,为了展示IsAlive属性。
Start()方法,是让这个线程启动。
接下来看一下运行结果(为了方便查看结果,我把循环都调成了6次):
可以看到,执行中的IsAlive属性是true,睡了200秒,线程执行完毕,IsAlive属性变为flase。
Join方法
线程调用join()方法,是指示CPU该线程交出自己的执行权(也就是该线程处于阻塞状态),直到其它线程执行执行完毕,Join()方法有个毫秒值重载,用于设置交出执行权多少时间。将上方代码更改后:
代码语言:javascript复制 Thread thread=new Thread(() =>
{
for (int i = 0; i <= 20; i )
{
Console.WriteLine($"{Thread.CurrentThread.Name}=========>{i}");
if (i == 10)
{
Thread.CurrentThread.Join(200);
}
}
});
thread.Name = "BackgroundThread";
thread.IsBackground = true;
thread.Start();
for (int i = 0; i <= 20; i )
{
Console.WriteLine($"main=========>{i}");
}
Thread.Sleep(200);
执行结果为:
当在i=10的时候,后台线程交出了执行权200毫秒,这期间只有主线程在工作。
线程的优先级
线程的优先级是可以设置的,但是,这仅仅是人为了提高了线程的优先级,至于真正的调配还得看CPU,所以一般多线程开发,是很繁琐的事情,维护起来也困难,所以多线程技术需要慎用,不能滥用。
线程优先级有个枚举类,源码如下:
代码语言:javascript复制public enum ThreadPriority
{
Lowest,//优先级最低
BelowNormal,//低于正常
Normal,//正常
AboveNormal,//较高
Highest,//最高
}
当我将其优先级更改为最高时,也并不能决定它是最快执行的,所以优先级的设置只是理论上的。
线程池
线程池是系统事先创建好的一堆后台线程,当一个程序需要一个后台线程执行一个不太重要的线程,并且代码简短的话,可以使用线程池,不用再自己new一个线程,这能略微提高性能。
代码语言:javascript复制ThreadPool.QueueUserWorkItem(s => Console.WriteLine(s),"helloworld");
使用 ThreadPool.QueueUserWorkItem()创建一个线程池线程,当线程池中有空闲线程时会取出一个线程来执行。须注意的是,第一个参数是一个带有一个参数的委托WaitCallback,第二个参数会作为这个委托的参数传入。
代码语言:javascript复制public delegate void WaitCallback(object state);
线程安全
不得不提的是,多线程存在线程安全问题,所以在开发时要注意。何为线程安全呢,举个例子,火车站售票,1000个人同时抢100张票,多线程情况下,有可能两个甚至多个线程同时抢到一个票,最后这几个线程都是一个座号,这显然是不合情理的。解决线程安全问题,就必须保证共享数据的同步性,也就是说同一时间只有一个线程访问共享的数据,关于线程安全的例子,我会在下一期进行讲解。
本节到此结束...