浅析.Net下的多线程编程

接下来我们对上面的代码进行略微修改,将每个线程的IsBackground属性都设置为true,则每个线程都是后台线程了。那么只要程序的主线程结束了,整个程序也就结束了。示例代码如下: 
  using System; 
   
  using System.Threading; 
   
  class MyApp 
   
  { 
   
  public static void Main () 
   
  { 
   
  for (int i=0; i<10; i++) { 
   
  Thread thread = new Thread (new ThreadStart (ThreadFunc)); 
   
  thread.IsBackground = true; 
   
  thread.Start (); 
   
  } 
   
  } 
   
  private static void ThreadFunc () 
   
  { 
   
  DateTime start = DateTime.Now; 
   
  while ((DateTime.Now - start).Seconds <5) 
   
  ; 
   
  }  
  } 

  既然前台线程和后台线程有这种差别,那么我们怎么知道该如何设置一个线程的IsBackground属性呢?下面是一些基本的原则:对于一些在后台运行的线程,当程序结束时这些线程没有必要继续运行了,那么这些线程就应该设置为后台线程。比如一个程序启动了一个进行大量运算的线程,可是只要程序一旦结束,那个线程就失去了继续存在的意义,那么那个线程就该是作为后台线程的。而对于一些服务于用户界面的线程往往是要设置为前台线程的,因为即使程序的主线程结束了,其他的用户界面的线程很可能要继续存在来显示相关的信息,所以不能立即终止它们。这里我只是给出了一些原则,具体到实际的运用往往需要编程者的进一步仔细斟酌。 

  线程优先级 

  一旦一个线程开始运行,线程调度程序就可以控制其所获得的CPU时间。如果一个托管的应用程序运行在Windows机器上,则线程调度程序是由Windows所提供的。在其他的平台上,线程调度程序可能是操作系统的一部分,也自然可能是.Net框架的一部分。不过我们这里不必考虑线程的调度程序是如何产生的,我们只要知道通过设置线程的优先级我们就可以使该线程获得不同的CPU时间。 

  线程的优先级是由Thread.Priority属性控制的,其值包含:ThreadPriority.Highest、ThreadPriority.AboveNormal、ThreadPriority.Normal、ThreadPriority.BelowNormal和ThreadPriority.Lowest。从它们的名称上我们自然可以知道它们的优先程度,所以这里就不多作介绍了。 
   
  线程的默认优先级为ThreadPriority.Normal。理论上,具有相同优先级的线程会获得相同的CPU时间,不过在实际执行时,消息队列中的线程阻塞或是操作系统的优先级的提高等原因会导致具有相同优先级的线程会获得不同的CPU时间。不过从总体上来考虑仍可以忽略这种差异。你可以通过以下的方法来改变一个线程的优先级。 

  thread.Priority = ThreadPriority.AboveNormal; 

  或是: 

  thread.Priority = ThreadPriority.BelowNormal; 

  通过上面的第一句语句你可以提高一个线程的优先级,那么该线程就会相应的获得更多的CPU时间;通过第二句语句你便降低了那个线程的优先级,于是它就会被分配到比原来少的CPU时间了。你可以在一个线程开始运行前或是在它的运行过程中的任何时候改变它的优先级。理论上你还可以任意的设置每个线程的优先级,不过一个优先级过高的线程往往会影响到其他线程的运行,甚至影响到其他程序的运行,所以最好不要随意的设置线程的优先级。  
   
  挂起线程和重新开始线程 

  Thread类分别提供了两个方法来挂起线程和重新开始线程,也就是Thread.Suspend能暂停一个正在运行的线程,而Thread.Resume又能让那个线程继续运行。不像Windows内核,.Net框架是不记录线程的挂起次数的,所以不管你挂起线程过几次,只要一次调用Thread.Resume就可以让挂起的线程重新开始运行。 

  Thread类还提供了一个静态的Thread.Sleep方法,它能使一个线程自动的挂起一定的时间,然后自动的重新开始。一个线程能在它自身内部调用Thread.Sleep方法,也能在自身内部调用Thread.Suspend方法,可是一定要别的线程来调用它的Thread.Resume方法才可以重新开始。这一点是不是很容易想通的啊?下面的例子显示了如何运用Thread.Sleep方法: 

  while (ContinueDrawing) {   
  DrawNextSlide ();  
  Thread.Sleep (5000);  
  } 

  终止线程 
   在托管的代码中,你可以通过以下的语句在一个线程中将另一个线程终止掉: 
  thread.Abort (); 
  下面我们来解释一下Abort()方法是如何工作的。因为公用语言运行时管理了所有的托管的线程,同样它能在每个线程内抛出异常。Abort()方法能在目标线程中抛出一个ThreadAbortException异常从而导致目标线程的终止。不过Abort()方法被调用后,目标线程可能并不是马上就终止了。因为只要目标线程正在调用非托管的代码而且还没有返回的话,该线程就不会立即终止。而如果目标线程在调用非托管的代码而且陷入了一个死循环的话,该目标线程就根本不会终止。不过这种情况只是一些特例,更多的情况是目标线程在调用托管的代码,一旦Abort()被调用那么该线程就立即终止了。 

  在实际应用中,一个线程终止了另一个线程,不过往往要等那个线程完全终止了它才可以继续运行,这样的话我们就应该用到它的Join()方法。示例代码如下: 

  thread.Abort (); // 要求终止另一个线程   
  thread.Join (); // 只到另一个线程完全终止了,它才继续运行 

  但是如果另一个线程一直不能终止的话(原因如前所述),我们就需要给Join()方法设置一个时间限制,方法如下: 

  thread.Join (5000); // 暂停5秒 
  
  这样,在5秒后,不管那个线程有没有完全终止,本线程就强行运行了。该方法还返回一个布尔型的值,如果是true则表明那个线程已经完全终止了,而如果是false的话,则表明已经超过了时间限制了。 
   
  时钟线程 
  
  .Net框架中的Timer类可以让你使用时钟线程,它是包含在System.Threading名字空间中的,它的作用就是在一定的时间间隔后调用一个线程的方法。下面我给大家展示一个具体的实例,该实例以1秒为时间间隔,在控制台中输出不同的字符串,代码如下: 
  
  using System;  
  using System.Threading;  
  class MyApp  
  { 
  private static bool TickNext = true;  
  public static void Main ()  
  {  
  Console.WriteLine ("Press Enter to terminate...");  
  TimerCallback callback = new TimerCallback (TickTock);  
  Timer timer = new Timer (callback, null, 1000, 1000);   
  Console.ReadLine ();   
  }   
  private static void TickTock (object state)    
  {   
  Console.WriteLine (TickNext ? "Tick" : "Tock");   
  TickNext = ! TickNext;    
  }    
  }  
  从上面的代码中,我们知道第一个函数回调是在1000毫秒后才发生的,以后的函数回调也是在每隔1000毫秒之后发生的,这是由Timer对象的构造函数中的第三个参数所决定的。程序会在1000毫秒的时间间隔后不断的产生新线程,只到用户输入回车才结束运行。不过值得注意的是,虽然我们设置了时间间隔为1000毫秒,但是实际运行的时候往往并不能非常精确。因为Windows操作系统并不是一个实时系统,而公用语言运行时也不是实时的,所以由于线程调度的千变万化,实际的运行效果往往是不能精确到毫秒级的,但是对于一般的应用来说那已经是足够的了,所以你也不必十分苛求。 

  小结  
  本文介绍了在.Net下进行多线程编程所需要掌握的一些基本知识。从文章中我们可以知道在.Net下进行多线程编程相对以前是有了大大的简化,但是其功能并没有被削弱。使用以上的一些基本知识,读者就可以试着编写.Net下的多线程程序了。不过要编写出功能更加强大而且Bug少的多线程应用程序,读者需要掌握诸如线程同步、线程池等高级的多线程编程技术。读者不妨参考一些操作系统方面或是多线程编程方面的技术丛书。
posted @ 2006-08-03 15:15  随风而逝  阅读(487)  评论(0编辑  收藏  举报