1、概念
1.0 线程的和进程的关系以及优缺点
windows系统是一个多线程的操作系统。一个程序至少有一个进程,一个进程至少有一个线程。进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线程称为主线程。例如当我们创建一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要 是产生新的线程和执行程序。C#是一门支持多线程的编程语言,通过Thread类创建子线程,引入using System.Threading命名空间。
多线程的优点:
|
1
2
|
1、 多线程可以提高CPU的利用率,因为当一个线程处于等待状态的时候,CPU会去执行另外的线程2、 提高了CPU的利用率,就可以直接提高程序的整体执行速度 |
多线程的缺点:
|
1
2
3
|
1、线程开的越多,内存占用越大2、协调和管理代码的难度加大,需要CPU时间跟踪线程3、线程之间对资源的共享可能会产生可不遇知的问题 |
1.1 前台线程和后台线程
C#中的线程分为前台线程和后台线程,线程创建时不做设置默认是前台线程。即线程属性IsBackground=false。
Thread.IsBackground = false;//false:设置为前台线程,系统默认为前台线程。
区别以及如何使用:
这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序。
线程是寄托在进程上的,进程都结束了,线程也就不复存在了!
只要有一个前台线程未退出,进程就不会终止!即说的就是程序不会关闭!(即在资源管理器中可以看到进程未结束。)
1.3 多线程的创建
下面的代码创建了一个子线程,作为程序的入口mian()函数所在的线程即为主线程,我们通过Thread类来创建子线程,Thread类有 ThreadStart 和 ParameterizedThreadStart类型的委托参数,我们也可以直接写方法的名字。线程执行的方法可以传递参数(可选),参数的类型为object,写在Start()里。
class Program
{
//我们的控制台程序入口是main函数。它所在的线程即是主线程
static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = "子线程";
//thread.Start("王建"); //在此方法内传递参数,类型为object,发送和接收涉及到拆装箱操作
thread.Start();
Console.ReadKey();
}
public static void ThreadMethod(object parameter) //方法内可以有参数,也可以没有参数
{
Console.WriteLine("{0}开始执行。", Thread.CurrentThread.Name);
}
}
首先使用new Thread()创建出新的线程,然后调用Start方法使得线程进入就绪状态,得到系统资源后就执行,在执行过程中可能有等待、休眠、死亡和阻塞四种状态。正常执行结束时间片后返回到就绪状态。如果调用Suspend方法会进入等待状态,调用Sleep或者遇到进程同步使用的锁机制而休眠等待。具体过程如下图所示:
2、线程的基本操作
线程和其它常见的类一样,有着很多属性和方法,参考下表:
2.1 线程的相关属性
我们可以通过上面表中的属性获取线程的一些相关信息,下面是代码展示和输出结果:
static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = "子线程";
thread.Start();
StringBuilder threadInfo = new StringBuilder();
threadInfo.Append(" 线程当前的执行状态: " + thread.IsAlive);
threadInfo.Append("\n 线程当前的名字: " + thread.Name);
threadInfo.Append("\n 线程当前的优先级: " + thread.Priority);
threadInfo.Append("\n 线程当前的状态: " + thread.ThreadState);
Console.Write(threadInfo);
Console.ReadKey();
}
public static void ThreadMethod(object parameter)
{
Console.WriteLine("{0}开始执行。", Thread.CurrentThread.Name);
}
输输出结果:
2.2 线程的相关操作
2.2.1 Abort()方法
Abort()方法用来终止线程,调用此方法抛出一个ThreadAbortException异常从而导致目标线程的终止。下面代码演示:
static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = "小A";
thread.Start();
Console.ReadKey();
}
public static void ThreadMethod(object parameter)
{
Console.WriteLine("我是:{0},我要终止了", Thread.CurrentThread.Name);
//开始终止线程
Thread.CurrentThread.Abort();
//下面的代码不会执行
for (int i = 0; i < 10; i++)
{
Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
}
}
执行结果:和我们想象的一样,下面的循环没有被执行
2.2.2 ResetAbort()方法
Abort方法可以通过跑出ThreadAbortException异常中止线程,而使用ResetAbort方法可以取消中止线程的操作,下面通过代码演示使用 ResetAbort方法。
static void Main(string[] args)
{
Thread thread = new Thread(ThreadMethod); //执行的必须是无返回值的方法
thread.Name = "小A";
thread.Start();
Console.ReadKey();
}
public static void ThreadMethod(object parameter)
{
try
{
Console.WriteLine("我是:{0},我要终止了", Thread.CurrentThread.Name);
//开始终止线程
Thread.CurrentThread.Abort();
}
catch(ThreadAbortException ex)
{
Console.WriteLine("我是:{0},我又恢复了", Thread.CurrentThread.Name);
//恢复被终止的线程
Thread.ResetAbort();
}
for (int i = 0; i < 10; i++)
{
Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
}
}
执行结果:
2.2.3 Sleep()方法
Sleep()方法调已阻塞线程,是当前线程进入休眠状态,在休眠过程中占用系统内存但是不占用系统时间,当休眠期过后,继续执行,声明如下:
public static void Sleep(TimeSpan timeout); //时间段
public static void Sleep(int millisecondsTimeout); //毫秒数
实例代码:
static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = "小A";
threadA.Start();
Console.ReadKey();
}
public static void ThreadMethod(object parameter)
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
Thread.Sleep(300); //休眠300毫秒
}
}
将上面的代码执行以后,可以清楚的看到每次循环之间相差300毫秒的时间。
2.2.4 join()方法
Join方法主要是用来阻塞调用线程,直到某个线程终止或经过了指定时间为止。官方的解释比较乏味,通俗的说就是创建一个子线程,给它加了这个方法,其它线程就会暂停执行,直到这个线程执行完为止才去执行(包括主线程)。她的方法声明如下:
public void Join(); public bool Join(int millisecondsTimeout); //毫秒数 public bool Join(TimeSpan timeout); //时间段
为了验证上面所说的,我们首先看一段代码:
static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = "小A";
Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = "小B";
threadA.Start();
//threadA.Join();
threadB.Start();
//threadB.Join();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("我是:主线程,我循环{1}次", Thread.CurrentThread.Name, i);
Thread.Sleep(300); //休眠300毫秒
}
Console.ReadKey();
}
public static void ThreadMethod(object parameter)
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
Thread.Sleep(300); //休眠300毫秒
}
}
因为线程之间的执行是随机的,所有执行结果和我们想象的一样,杂乱无章!但是说明他们是同时执行的。
现在我们把代码中的 ThreadA.join()方法注释取消,首先程序中有三个线程,ThreadA、ThreadB和主线程,首先主线程先阻塞,然后线程ThreadB阻塞,ThreadA先执行,执行完毕以后ThreadB接着执行,最后才是主线程执行。
看执行结果:
2.2.5 Suspent()和Resume()方法
其实在C# 2.0以后, Suspent()和Resume()方法已经过时了。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend()。
static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = "小A";
threadA.Start();
Thread.Sleep(3000); //休眠3000毫秒
threadA.Resume(); //继续执行已经挂起的线程
Console.ReadKey();
}
public static void ThreadMethod(object parameter)
{
Thread.CurrentThread.Suspend(); //挂起当前线程
for (int i = 0; i < 10; i++)
{
Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i);
}
}
执行上面的代码。窗口并没有马上执行 ThreadMethod方法输出循环数字,而是等待了三秒钟之后才输出,因为线程开始执行的时候执行了Suspend()方法挂起。然后主线程休眠了3秒钟以后又通过Resume()方法恢复了线程threadA。
2.2.6 线程的优先级
如果在应用程序中有多个线程在运行,但一些线程比另一些线程重要,这种情况下可以在一个进程中为不同的线程指定不同的优先级。线程的优先级可以通过Thread类Priority属性设置,Priority属性是一个ThreadPriority型枚举,列举了5个优先等级:AboveNormal、BelowNormal、Highest、Lowest、Normal。公共语言运行库默认是Normal类型的。见下图:
直接上代码来看效果:
static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadA.Name = "A";
Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法
threadB.Name = "B";
threadA.Priority = ThreadPriority.Highest;
threadB.Priority = ThreadPriority.BelowNormal;
threadB.Start();
threadA.Start();
Thread.CurrentThread.Name = "C";
ThreadMethod(new object());
Console.ReadKey();
}
public static void ThreadMethod(object parameter)
{
for (int i = 0; i < 500; i++)
{
Console.Write(Thread.CurrentThread.Name);
}
}