简单理解
Thread:是一个指令序列,个体对象。
Threadpool:在使用Thread的过程中,程序员要为每个希望并发的序列new一个线程,很麻烦,因此希望有一个统一管理线程的方法,程序员就不需要关注线程的申请管理问题,所以就对Thread进行一系列封装,有了ThreadPool。使用Threadpool,把需要并发的序列添加进线程池,线程池根据其线程列表中的线程的空闲情况,动态为并发序列申请线程。
Task:再后来,程序员发现在使用Threadpool的过程当中还是存在很多不便,比如:(1)ThreadPool不支持线程的取消、完成、失败通知等交互性操作;(2)ThreadPool不支持线程执行的先后次序;那么怎么办,那就继续对ThreadPool进行封装呗,于是.net Framework4.0有了TPL和Task。
Thread
选用Thread的一个重要原因之一是因为它的Thread.Sleep()成员,因为它在Task或ThreadPool中都没有等价物。不过如果不会带来太多没必要的复杂性,可以考虑用一个计时器代替Sleep()。
对于Thread这个知识点,重点在于同步、死锁和竞态等问题,感觉关键点在于对lock()和WaitHandler的理解。这里,介绍下参数的传递和获取。
下面这部分主要查看MSDN的文档:http://msdn.microsoft.com/en-us/library/wkays279.aspx
- 向线程传递参数
因为Thread的构造函数需要传递一个无返回类型和无参的委托引用,因此最理想的办法是把需要传递给线程的方法封装在一个类里面,然后将需要传递给这个线程的参数定义为该类的字段。比如,要把下面这个带参函数通过线程调用:
1 public double CalArea(double length,double width) 2 { 3 double Area=length*width; 4 return Area; 5 }
可使用如下方法进行:
1 public class AreaClass 2 { 3 public double width; 4 public double length; 5 public double area; 6 public void CalArea() 7 { 8 area=width*length; 9 } 10 } 11 12 public void TestAreaCal() 13 { 14 AreaClass areaCal=new AreaClass(); 15 System.Threading.Thread thread1=new System.Threading.Thread(areaCal.CalArea); 16 areaCal.width=2; 17 areaCal.length=3; 18 thread1.start(); 19 }
但要注意,如果在执行完thread1.start()之后,立刻查看areaCal.area的值,并不一定就存在了。要获得有效的返回值得简单方法将在下面介绍。
- 获取线程返回值
简单方法是使用BackgroundWorker类。利用BackgroundWorker类管理你的线程,并在线程执行完毕后产生一个事件,然后在对应的事件处理函数中处理结果。
1 class AreaClass2 2 { 3 public double Base; 4 public double Height; 5 public double CalcArea() 6 { 7 // Calculate the area of a triangle. 8 return 0.5 * Base * Height; 9 } 10 } 11 12 private System.ComponentModel.BackgroundWorker BackgroundWorker1 13 = new System.ComponentModel.BackgroundWorker(); 14 15 private void TestArea2() 16 { 17 InitializeBackgroundWorker(); 18 19 AreaClass2 AreaObject2 = new AreaClass2(); 20 AreaObject2.Base = 30; 21 AreaObject2.Height = 40; 22 23 // Start the asynchronous operation. 24 BackgroundWorker1.RunWorkerAsync(AreaObject2); 25 } 26 27 private void InitializeBackgroundWorker() 28 { 29 // Attach event handlers to the BackgroundWorker object. 30 BackgroundWorker1.DoWork += 31 new System.ComponentModel.DoWorkEventHandler(BackgroundWorker1_DoWork); 32 BackgroundWorker1.RunWorkerCompleted += 33 new System.ComponentModel.RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted); 34 } 35 36 private void BackgroundWorker1_DoWork( 37 object sender, 38 System.ComponentModel.DoWorkEventArgs e) 39 { 40 AreaClass2 AreaObject2 = (AreaClass2)e.Argument; 41 // Return the value through the Result property. 42 e.Result = AreaObject2.CalcArea(); 43 } 44 45 private void BackgroundWorker1_RunWorkerCompleted( 46 object sender, 47 System.ComponentModel.RunWorkerCompletedEventArgs e) 48 { 49 // Access the result through the Result property. 50 double Area = (double)e.Result; 51 MessageBox.Show("The area is: " + Area.ToString()); 52 }
ThreadPool
ThreadPool是一个静态类。
管理过程:
许多程序生成的线程有一大半时间处在休眠状态,直到产生一个事件唤醒它们,另一部分线程只会被周期性地唤醒以更改状态信息。ThreadPool可以通过为你的应用程序提供由系统管理的线程来提高应用程序对线程的使用率。线程池中会有一个线程监看线程池队列中的等待操作,当线程池中的线程完成它的任务后,它就会回到等待线程的队列当中,等待重新使用。如果线程池中的所有线程都在运行,那么新的任务会加进等待队列中。对线程的重复使用降低了为每一个任务都开启一个新线程的造成的程序开销。
特点:
ThreadPool里面的线程管理是由类库(或操作系统)完成的,而不是由应用程序进行管理。
线程池里面的线程都是后台线程(即它们的isBackgroud属性为true)。也就意味着当所有前台线程都运行完毕后,线程池的线程不会保持程序的运行。
线程池一般使用在服务应用程序上。每个进来的请求都分配给线程池中的每一个线程,这样就可以异步处理各个请求。
使用:
线程池的简单使用可通过QueueUserWorkItem方法进行。如果需要向任务中传递参数,可以通过传递一个state object来进行,如果有多个参数要传递,可将多个参数封装在结构或对象的字段当中。但要注意,一旦一个方法加入了线程队列,就无法取消该方法的执行:
public static void Main() { System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(ASynTask),ASynData); //other code snippets...... } public void ASynTask(object state) { //codes need to executed asynchronously }
线程池的大小可以通过GetMaxThreads或SetMaxThreads方法访问或设置,但注意过大或过小都会影响程序的性能。
另外,获取ThreadPool返回值有两种方法,具体查看上面Thread所讲的办法。
Task和TPL
关于Task和TPL的知识还是以后再详细介绍。这里简单讲下Task的主要特征。
- Task包含一个ContinueWith()方法,它的作用是将任务链接起来,只要链中的第一个任务完成,就会触发已登记在它之后执行的其它任务。还有个有趣的地方,可以用ContinueWith()添加多个任务,在先驱任务(父任务)完成后,添加的所有任务都会开始并行运行。另外,可以在ContinueWith()语句有一个TaskContinuationOpitons枚举值,通过指定该值,可根据先驱任务的运行情况决定是否运行子任务。
- 支持协作式取消任务。通过使用System.Threading.CancellationToken来进行。一个线程通过设置CancellationToken(struct类型)请求取消任务,运行的线程通过检查该CancellationToken决定是否继续运行。还有个值得关注的地方就是,通过CancellationToken终止的线程,如果不会在某些方面造成破坏,不会使线程的Faulted属性为true。
1 public static void Main() 2 { 3 CancellationTokenSource cancellationTokenSource=new CancellationTokenSource(); 4 Task task.Task.Factory.StartNew(()=>LoopWork(cancellationTokenSource.Token),cancellationTokenSource.Token); 5 Thread.sleep(1000); 6 cancellationTokenSource.Cancel(); 7 task.wait(); 8 } 9 10 private static void LoopWork(CancellationToken cancellationToken) 11 { 12 while(!cancellationToken.IsCancellationRequested) 13 { 14 //work need to be done 15 } 16 }
Task还支持IDisposable方法,这是支持Wait()功能所需要的。
并行迭代
在使用for循环迭代时,处理器会按顺序依次迭代。但如果每次迭代之间互不影响,而且如果系统有多个处理器,我们可以让每个处理器都负责一个迭代,从而提高效率。
相关类:System.Threading.Tasks.Parallel。
相关方法:Parallel.For()和Parallel.Foreach()
注意三点:计算机具有多个处理器,并行迭代的顺序不是固定的,在使用时还要同时考虑迭代内部代码的非原子性带来的竞态问题。
1 //将10000个小写单词变成大写 2 static void Main() 3 { 4 int iterations=10000; 5 string[] word=new string[iterations]; 6 //此处初始化word,输入10000个小写单词 7 Parallel.For(0,iterations,(i)=> 8 { 9 word[i]=word[i].toUpper(); 10 }); 11 }