概述
线程是程序中独立的指令流。进程包含资源,如Window句柄、文件系统句柄或其他内核对象。每个进程都分配了虚拟内存。一个进程至少包含一个线程。操作系统会调用线程。线程有一个优先级、实际上正在处理的程序的位置计数器、一个存储其局部变量的栈。每个线程都有自己的栈,但程序代码的内存和堆由一个进程的所有线程共享。这使一个进程的所有线程之间的通信非常快——该进程的所有线程都寻址相同的虚拟内存。但是,这也使处理比较困难,因为多个线程可以修改同一个内存位置。
进程管理的资源包括虚拟内存和Windows句柄,其中至少包含一个线程。线程是运行程序所必需的。
异步委托
委托使用线程池来完成异步任务。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Diagnostics; namespace test1 { using cs = Console; class Program { //delegate int fun static Action<object> put = cs.WriteLine; static void Main(string[] args) { Func<int, int, int> fun = TakesAWhile; int nType = 3; if (nType == 1) { IAsyncResult returnVal = fun.BeginInvoke(1, 1000, null, null); //投票 while (!returnVal.IsCompleted) { cs.WriteLine("{0} : 1", 1); Thread.Sleep(50); } put(fun.EndInvoke(returnVal)); } else if (nType == 2) { IAsyncResult returnVal = fun.BeginInvoke(1, 1000, null, null); //句柄 while (true) { cs.WriteLine("{0} : 1", 1); if (returnVal.AsyncWaitHandle.WaitOne(50, true)) { break; } } put(fun.EndInvoke(returnVal)); } else { //异步回调 IAsyncResult returnVal = fun.BeginInvoke(1, 1000, (IAsyncResult ar) => { if (ar == null) throw new ArgumentNullException("ar"); Func<int, int, int> d1 = ar.AsyncState as Func<int, int, int>; Trace.Assert(d1 != null, "Invalid object type"); put(fun.EndInvoke(ar)); }, null); Thread.Sleep(2000); } } static int TakesAWhile(int data, int ms) { cs.WriteLine("TakesAWhile started"); Thread.Sleep(ms); cs.WriteLine("TakesAWhile completed"); return ++data; } } }
1.投票
调用委托类型的Endhvokco方法。Endhvokeo方法会一直等待,直到委托完成其任务为止。
注意:如果在委托结束之前不等待委托完成其任务就结束主线程,委托线程就会停止。
2.等待句柄
这个属性返回一个WaitHandle类型的对象,它可以等待委托线程完成其任务。WaitOne()方法将一个超时时间作为可选的第一个参数,在其中可以定义要等待的最长时间。这里设置为50毫秒。如果发生超时,WaitOne()方法就返回false。
3.异步回调
使用回调方法,必须注意这个方法从委托线程中调动,而不是从主线程中调用。
Thread类
1.给线程传递数据
给线程传递一些数据可以采用两种方式。 一 种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,另 一 种方式是创建一 个自定义类 ,把线程的方法定义为实例方法,这 样就可以初始化实例的数据,之 后启动线程。
1. 使用ParameterizedThreadStart委托参数的Thread构造函数,线程的入口点必须有 一 个object类型的参数 ,且返回void类型。
using cs = Console;
class Program
{
static Action<object> put = cs.WriteLine;
static void Main(string[] args)
{
Thread thread = new Thread(print);thread.Start(new Student { name = "Mical", id = 112 });
}static void print(object obj)
{
Student stu = (Student)obj;
cs.WriteLine("name:{0}, id:{1}", stu.name, stu.id);
}
}struct Student {
public string name;
public int id;
}2.自定义类
using cs = Console; class Program { static Action<object> put = cs.WriteLine; static void Main(string[] args) { print pt = new print { stu = new Student { name = "Mical", id = 112 } }; Thread thread = new Thread(pt.put); thread.Start(); } } class print { public Student stu; public void put() { cs.WriteLine("name:{0}, id:{1}", stu.name, stu.id); } } struct Student { public string name; public int id; }
2.后台线程
只要有一个前台线程在运行 ,应用程序的进程就在运行。如果多个前台线程在运行 ,而Main方法结束了,应用程序的进程就仍然是激活的 ,直到所有前台线程完成其任务为止。
在默认情况下,用 Thread类创建的线程是前台线程。线程池中的线程总是后台线程。在用Tread类创建线程时 ,可以设置IsBackground属性,以确定该线程是前台线程还是后台线程。using cs = Console; class Program { static Action<object> put = cs.WriteLine; static void Main(string[] args) { print pt = new print { stu = new Student { name = "Mical", id = 112 } }; Thread thread = new Thread(pt.put) { Name = "myThread", IsBackground = true };//设置成true,学生信息将不能正常输出,主线程结束的时候该线程就结束了; thread.Start(); } } class print { public Student stu; public void put() { Thread.Sleep(1000);//确保主线程结束 cs.WriteLine("name:{0}, id:{1}", stu.name, stu.id); } } struct Student { public string name; public int id; }
3.线程的优先级
线程有操作系统调度,给线程指定优先级,就可以影响调度顺序。
已经在CPU上运行的线程,如果在等待资源,它就会停止运行,并释放CPU。等待情况有多种,例如,睡眠指令、等待磁盘I/O的完成,等待网络包的到达等。如果线程不主动释放CUP,线程调度器就会抢占该线程。如果线程有个时间量,它就会继续使用CPU。如果优先级相同的多个线程等待使用CPU,线程调度器就会使用一个循环调度规则,将CPU逐个交个线程使用。如果线程被其它线程抢占,它就会排在队列的最后。
只有优先级相同的多个线程在运行,才用的上时间量和循环规则。优先级是动态的。如果线程是CPU密集型的(一直需要CPU,且不等待资源),其优先级应就低于用该线程定义的基本优先级。
4.线程控制
调用Thread对象的Start()方法,可以创建线程。但是,在调用start()方法后,新线程仍不是处于Running状态,而是处于Unstarted状态。只要操作系统的线程调度器选择了要运行的线程,线程就会改为Running状态。读取Thread.ThreadState属性,就可以获得线程的当前状态。
使用Thread.Sleep()方法,会使线程处于WaitSleepJoin状态,在经历Sleep()方法定义的时间段后,线程就会等待再次被唤醒。
要停止另一个线程,可以调用Thread.Abort()方法。调用这个方法时,会在接到终止命令的线程中抛出一 个ThreadAbortException类型的异常。用一个处理程序捕获这个异常,线程可以在结束前完成一些清理工作。线程还可以在接收到调用Thread.ResetAbort()方法的结果ThreadAbortException异常后继续运行。 如果线程没有重置终止,接收到终止请求的线程的状态就从AbortRequested改为Aborted。
如果需要等待线程的结束,就可以调用Thresd.Join()方 法。Thread.Join()方法会停止当前线程 ,并把它设置为WaitSleepJoin状态,直到加入的线程完成为止。
线程池
创建线程需要时间。如果有不同的小任务要完成,就可以事先创建许多线程,在应完成这些任务时发出请求。这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。不需要自己创建这样一个列表。该列表由ThreadPool类托管。这个类会在需要时增减池中线程线程数,直到最大的线程数。池中的最大线程数是可配置的。在双核 CPU中,默认设置为1023个工作线程和1000个I/O线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。如果有更多的作业要处理,线池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。
using cs = Console; class Program { static void Main() { int nWorkerThreads; int nCompletionPortThreads; ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads); Console.WriteLine("Max worker threads:{0}, " + "I/O completion threads:{1}", nWorkerThreads, nCompletionPortThreads); for(int i=0; i<5; i++) { ThreadPool.QueueUserWorkItem(JobForAthread); } Thread.Sleep(3000); } static void JobForAthread(object state) { cs.WriteLine(state); for (int i = 0; i < 3; i++) { cs.WriteLine("loop {0}, running inside pooled thread{1}", i, Thread.CurrentThread.ManagedThreadId); Thread.Sleep(50); } } }