由于多线程的内容比较多我会用几篇文章来讲解。
多线程在我们日常开发过程中用的很多,上一篇“.NET简谈组件程序设计之(异步委托) ”详细的讲解了基于委托的多线程使用,委托是基于后台线程池的原理,这篇文章将主要介绍直接使用Thread对象来实现多线程。
当然使用Thread没有使用Delegate那么容易,毕竟多线程跟异步调用是两个相差很大的技术方向,我也是略懂点皮毛,在此献丑给大家,如有讲的不对的地方还请指出。[王清培版权所有,转载请给出署名]
我们先来理解几个概念,以方便我们学习。
后台线程与前台线程
前台线程:什么叫前台线程,就是我们使用默认的Thread创建出来的没有进行IsBackground属性设置的都是前台线程,因为默认IsBackground是false。前台线程是明确任务的,也就是任何一个前台线程没有结束之前程序是不会自动退出的,除非强制关闭应用程序。
后台线程:后台线程是针对前台线程来说的,将Thread.IsBackground设置为true就是后台线程,后台线程是为前台线程服务的,就是说后台线程没有很强的生命力,只要前台线程都结束了,后台线程都强制结束,哪怕任务还没有完成都不行。所以我们在使用的时候要看情况进行选择。[王清培版权所有,转载请给出署名]
线程的切换
我们来看一段代码,以方便引入主题。
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Runtime.Remoting; using System.Runtime.Remoting.Contexts; namespace ConsoleApplication1.多线程和并发管理 { public class MyClass { public void ShowMessage() { Thread currentthread = Thread.CurrentThread; Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId); } } }
这是一段很简单的代码,就是一个ShowMessage方法,在方法里面有一个获取当前上下文线程的静态属性Thread.CurrentThread,然后输入该线程的名称和托管ID号;
namespace ConsoleApplication1.多线程和并发管理 { public static class Program { static void Main(string[] args) { Thread currentthread = Thread.CurrentThread; currentthread.Name = "主线程"; Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId); MyClass myclass = new MyClass(); ThreadStart start = new ThreadStart(myclass.ShowMessage); Thread thread = new Thread(start); thread.Name = "子线程"; thread.Start(); Thread.Sleep(1000);//休眠,线程切换 Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId); Console.Read(); } } }
这是调用代码,我先给主线程起个名字,然后输出。我新建了一个thread线程,这是子线程,调用我们上面定义的方法,用同样的Thread.CurrentThread来获取当前上下文线程,最后让主线程休眠一秒钟。
通过该图我们能清楚的看见,系统在后台自动帮我们进行线程切换,用同一个静态变量就可以获取到当前线程对象。
通过Sleep方法是让当前线程休眠指定的时间断,哪怕当前线程正在CPU上运行着,一旦调用Sleep就立刻放弃CPU给它的时间片,进入阻塞状态。
[一个线程仅仅是一个进程中的执行路径]
其实线程是执行路径,系统中维护着一个执行路径的命令集合,当我们开启了多个线程的时候其实就是往着个命令集合中存放了很多要执行的命令而已,换句话说命令就是线程队列,用CPU 对它进行时间片的执行。
那么线程是肯定需要一系列的状态的,这个状态时有OS帮我们维持着,因为线程是属于内核层的对象,只有OS才能实时监控着。我们只需要用就行了,有兴趣的朋友可以参考,杰夫瑞 (Jeffrey Richter)《Windows核心编程(第5版)》 一书。
让线程等待而不是切换
Sleep是强制放弃CPU的时间片,然后重新和其他线程一起参与CPU的竞争。用Sleep是会让线程放弃CPU的使用权,而如果我们换成 Thread.SpinWait(100000000),是不会放弃CPU的使用权的,只是让CPU去执行一段没有用的代码,当时间结束之后能立马继续执行,而不是和重新参与CPU的竞争。
在系统资源很丰富的情况下可能这点并不重要,但是在资源缺乏,CPU又不是很好的时候,我想这点还是能改善点性能的。
在此不得不提一个重要的概念,就是线程的调用方和线程主体,线程的调用方就是线程的客户端,是另外一个线程,而不是当前线程主体。
Thread.Join()连接线程
join方法从字面理解是连接的意思,刚接触真的很难理解,什么叫连接。请看一段代码:
Thread currentthread = Thread.CurrentThread; currentthread.Name = "主线程"; Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId); MyClass myclass = new MyClass(); ThreadStart start = new ThreadStart(myclass.ShowMessage); Thread thread = new Thread(start); thread.Name = "子线程"; thread.Start(); thread.Join();//阻塞子线程thread线程,直到子线程thread执行结束 Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId); Console.Read();
通过Join我们可以等待线程结束,连接的意思就是将我和我调用的thread线程连接起来,我要等你结束之后我才能继续执行,这里就是主线程和子线程的关系,只有子线程技术之后,主线程才能继续执行。
Thread.Abort终止线程
利用Abort可以终止一个在执行的线程,但是Abort会在线程上引发一个ThreadAbortException异常。
public void DoWork() { try { int i = 0; while (true) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "|" + i++); if (i == 100) { Console.WriteLine("---------------------------------------------"); Console.Read(); break;//退出当前线程执行,尽量不要用Abort结束 } } } catch (ThreadAbortException err) { Console.WriteLine(err.Message + "11"); } }
Thread currentthread = Thread.CurrentThread; Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId); MyClass myclass = new MyClass(); ThreadStart start = new ThreadStart(myclass.DoWork); Thread thread = new Thread(start); thread.Start(); Thread.Sleep(5000); Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId + "是否终止子线程"); thread.Abort(); Console.Read();
Thread.IsBackground = true后台线程
通过设置IsBackground可以让线程处于后台线程,只要前台线程结束,那么后台线程自动终止。
public void DoWork() { try { int i = 0; while (true) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "|" + i++); //if (i == 100) //{ // Console.WriteLine("---------------------------------------------"); // Console.Read(); // break;//退出当前线程执行,尽量不要用Abort结束 //} } } catch (ThreadAbortException err) { Console.WriteLine(err.Message + "11"); }
我们将一段代码注释掉。
Thread currentthread = Thread.CurrentThread; Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId); MyClass myclass = new MyClass(); ThreadStart start = new ThreadStart(myclass.DoWork); Thread thread = new Thread(start); thread.IsBackground = true; thread.Start(); Thread.Sleep(2000); Console.WriteLine(currentthread.Name + currentthread.ManagedThreadId);
这是调用代码。只要前台线程不结束,后台线程就一直执行。如果我们在最后加上一段Console.ReadLine();代码,那么后台线程会始终运行着。
这篇文章就先结束了,下一篇我们将学习关于同步域和上下文的概念。