在.NET中编写的程序将被自动的分配一个线程.让我们来看看用C#编程语言创建线程并且继续学习线程的知识。我们都知道.NET的运行时环境的主线程由Main ()方法来启动应用程序,而且.NET的编译语言有自动的垃圾收集功能,这个垃圾收集发生在另外一个线程里面,所有的这些都是后台发生的,让我们无法感觉到发生了什么事情.在这里默认的是只有一个线程来完成所有的程序任务,但是正如我们在第一篇文章讨论过的一样,有可能我们根据需要自己添加更多的线程让程序更好的协调工作。比如说我们的例子中,一个有用户输入的同时需要绘制图形或者完成大量的运算的程序,我们必须得增加一个线程,让用户的输入能够得到及时的响应,因为输入对时间和响应的要求是紧迫的,而另外一个线程负责图形绘制或者大量的运算。
.NET 基础类库的System.Threading命名空间提供了大量的类和接口支持多线程。这个命名空间有很多的类,我们将在这里着重讨论Thread这个类。
System.Threading.Thread类是创建并控制线程,设置其优先级并获取其状态最为常用的类。他有很多的方法,在这里我们将就比较常用和重要的方法做一下介绍:
Thread.Start():启动线程的执行;
Thread.Suspend():挂起线程,或者如果线程已挂起,则不起作用;
Thread.Resume():继续已挂起的线程;
Thread.Interrupt():中止处于 Wait或者Sleep或者Join 线程状态的线程;
Thread.Join():阻塞调用线程,直到某个线程终止时为止
Thread.Sleep():将当前线程阻塞指定的毫秒数;
Thread.Abort():以开始终止此线程的过程。如果线程已经在终止,则不能通过Thread.Start()来启动线程。
通过调用Thread.Sleep,Thread.Suspend或者Thread.Join可以暂停/阻塞线程。调用Sleep()和Suspend()方法意味着线程将不再得到CPU时间。这两种暂停线程的方法是有区别的,Sleep()使得线程立即停止执行,但是在调用Suspend()方法之前,公共语言运行时必须到达一个安全点。一个线程不能对另外一个线程调用Sleep()方法,但是可以调用Suspend()方法使得另外一个线程暂停执行。对已经挂起的线程调用Thread.Resume()方法会使其继续执行。不管使用多少次Suspend()方法来阻塞一个线程,只需一次调用Resume()方法就可以使得线程继续执行。已经终止的和还没有开始执行的线程都不能使用挂起。Thread.Sleep(int x)使线程阻塞x毫秒。只有当该线程是被其他的线程通过调用Thread.Interrupt()或者Thread.Abort()方法,才能被唤醒。
如果对处于阻塞状态的线程调用Thread.Interrupt()方法将使线程状态改变,但是会抛出ThreadInterupptedException异常,你可以捕获这个异常并且做出处理,也可以忽略这个异常而让运行时终止线程。在一定的等待时间之内,Thread.Interrupt()和Thread.Abort()都可以立即唤醒一个线程。
下面我们将说明如何从一个线程中止另外一个线程。在这种情况下,我们可以通过使用Thread.Abort()方法来永久销毁一个线程,而且将抛出ThreadAbortException异常。使终结的线程可以捕获到异常但是很难控制恢复,仅有的办法是调用Thread.ResetAbort()来取消刚才的调用,而且只有当这个异常是由于被调用线程引起的异常。因此,A线程可以正确的使用Thread.Abort()方法作用于B线程,但是B线程却不能调用Thread.ResetAbort()来取消Thread.Abort()操作。
Thread.Abort()方法使得系统悄悄的销毁了线程而且不通知用户。一旦实施Thread.Abort()操作,该线程不能被重新启动。调用了这个方法并不是意味着线程立即销毁,因此为了确定线程是否被销毁,我们可以调用Thread.Join()来确定其销毁,Thread.Join()是一个阻塞调用,直到线程的确是终止了才返回。但是有可能一个线程调用Thread.Interrupt()方法来中止另外一个线程,而这个线程正在等待Thread.Join()调用的返回。
尽可能的不要用Suspend()方法来挂起阻塞线程,因为这样很容易造成死锁。假设你挂起了一个线程,而这个线程的资源是其他线程所需要的,会发生什么后果。因此,我们尽可能的给重要性不同的线程以不同的优先级,用Thread.Priority()方法来代替使用Thread.Suspend()方法。
Thread类有很多的属性,这些重要的属性是我们多线程编程必须得掌握的。
Thread.IsAlive属性:获取一个值,该值指示当前线程的执行状态。如果此线程已启动并且尚未正常终止或中止,则为 true;否则为 false。
Thread.Name 属性:获取或设置线程的名称。
Thread.Priority 属性:获取或设置一个值,该值指示线程的调度优先级。
Thread.ThreadState 属性:获取一个值,该值包含当前线程的状态。
在下面的例子中,我们将看看怎么设置这些属性,在随后的例子中我们将详细的讨论这些属性。
创建一个线程,首先得实例化一个Thread类,在类得构造函数中调用ThreadStart委派。这个委派包含了线程从哪里开始执行。当线程启动后,Start()方法启动一个新的线程。下面是例子程序。
using System;
using System.Threading ;
namespace LearnThreads
{
class Thread_App
{
public static void First_Thread()
{
Console.WriteLine("First thread created");
Thread current_thread = Thread.CurrentThread;
string thread_details = "Thread Name: " + current_thread.Name + "\r\nThread State: " + current_thread.ThreadState.ToString()+"\r\n Thread Priority level:"+current_thread.Priority.ToString();
Console.WriteLine("The details of the thread are :"+ thread_details);
Console.WriteLine ("first thread terminated");
}
public static void Main()
{
ThreadStart thr_start_func = new ThreadStart (First_Thread);
Console.WriteLine ("Creating the first thread ");
Thread fThread = new Thread (thr_start_func);
fThread.Name = "first_thread";
fThread.Start (); //starting the thread
}
}
}
在这个例子中,创建了一个fThread的线程对象,这个线程负责执行First_Thread()方法里面的任务。当Thread的Start() 方法被调用时包含First_Thread()的地址ThreadStart的代理将被执行。
Thread状态
System.Threading.Thread.ThreadState属性定义了执行时线程的状态。线程从创建到线程终止,它一定处于其中某一个状态。当线程被创建时,它处在Unstarted状态,Thread类的Start() 方法将使线程状态变为Running状态,线程将一直处于这样的状态,除非我们调用了相应的方法使其挂起、阻塞、销毁或者自然终止。如果线程被挂起,它将处于Suspended状态,除非我们调用resume()方法使其重新执行,这时候线程将重新变为Running状态。一旦线程被销毁或者终止,线程处于Stopped状态。处于这个状态的线程将不复存在,正如线程开始启动,线程将不可能回到Unstarted状态。线程还有一个Background状态,它表明线程运行在前台还是后台。在一个确定的时间,线程可能处于多个状态。据例子来说,一个线程被调用了Sleep而处于阻塞,而接着另外一个线程调用Abort方法于这个阻塞的线程,这时候线程将同时处于WaitSleepJoin和AbortRequested状态。一旦线程响应转为Sle阻塞或者中止,当销毁时会抛出ThreadAbortException异常。
线程优先级
System.Threading.Thread.Priority枚举了线程的优先级别,从而决定了线程能够得到多少CPU时间。高优先级的线程通常会比一般优先级的线程得到更多的CPU时间,如果不止一个高优先级的线程,操作系统将在这些线程之间循环分配CPU时间。低优先级的线程得到的CPU时间相对较少,当这里没有高优先级的线程,操作系统将挑选下一个低优先级 的线程执行。一旦低优先级的线程在执行时遇到了高优先级的线程,它将让出CPU给高优先级的线程。新创建的线程优先级为一般优先级,我们可以设置线程的优先级别的值,如下面所示:
Highest
AboveNormal
Normal
BelowNormal
Lowest
结论:在这一部分,我们讨论了线程的创建何线程的优先级。System.Threading命名空间还包含了线程锁定、线程同步何通讯、多线程管理类以及死锁解决等等高级特性,在后面的部分我们将继续讨论这些内容。
http://pcedu.pconline.com.cn/empolder/net/cs/0507/655844_1.html
·创建线程:
创建一个新的Thread对象的实例。Thread的构造函数接受一个参数:
Thread DummyThread = new Thread( new ThreadStart(dummyFunction) );
·执行线程:
使用Threading命名空间里的start方法来运行线程:
DummyThread.Start ();
·组合线程:
经常会出现需要组合多个线程的情况,就是当某个线程需要其他线程的结束来完成自己的任务。假设DummyThread必须等待DummyPriorityThread来完成自己的任务,我们只需要这样做:
DummyPriorityThread.Join() ;
·暂停线程:
使得线程暂停给定的秒
DummyPriorityThread. Sleep ();
·中止线程:
如果需要中止线程可以使用如下的代码:
DummyPriorityThread.Abort();
http://webservices.ctocio.com.cn/wsnettec/63/8170563_1.shtml
多线程是许多操作系统所具有的特性,它能大大提高程序的运行效率,所以多线程编程技术为编程者广泛关注。目前微软的.Net战略正进一步推进,各种相关的技术正为广大编程者所接受,同样在.Net中多线程编程技术具有相当重要的地位。本文我就向大家介绍在.Net下进行多线程编程的基本方法和步骤。
开始新线程
在.Net下创建一个新线程是非常容易的,你可以通过以下的语句来开始一个新的线程:
Thread thread = new Thread (new ThreadStart (ThreadFunc));
thread.Start ();
第一条语句创建一个新的Thread对象,并指明了一个该线程的方法。当新的线程开始时,该方法也就被调用执行了。该线程对象通过一个System..Threading.ThreadStart类的一个实例以类型安全的方法来调用它要调用的线程方法。
第二条语句正式开始该新线程,一旦方法Start()被调用,该线程就保持在一个"alive"的状态下了,你可以通过读取它的IsAlive属性来判断它是否处于"alive"状态。下面的语句显示了如果一个线程处于"alive"状态下就将该线程挂起的方法:
if (thread.IsAlive) {
thread.Suspend ();
}
不过请注意,线程对象的Start()方法只是启动了该线程,而并不保证其线程方法ThreadFunc()能立即得到执行。它只是保证该线程对象能被分配到CPU时间,而实际的执行还要由操作系统根据处理器时间来决定。
一个线程的方法不包含任何参数,同时也不返回任何值。它的命名规则和一般函数的命名规则相同。它既可以是静态的(static)也可以是非静态的(nonstatic)。当它执行完毕后,相应的线程也就结束了,其线程对象的IsAlive属性也就被置为false了。下面是一个线程方法的实例:
public static void ThreadFunc()
{
for (int i = 0; i <10; i++) {
Console.WriteLine("ThreadFunc ", i);
}
}
前台线程和后台线程
.Net的公用语言运行时(Common Language Runtime,CLR)能区分两种不同类型的线程:前台线程和后台线程。这两者的区别就是:应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。
一个线程是前台线程还是后台线程可由它的IsBackground属性来决定。这个属性是可读又可写的。它的默认值为false,即意味着一个线程默认为前台线程。我们可以将它的IsBackground属性设置为true,从而使之成为一个后台线程。
下面的例子是一个控制台程序,程序一开始便启动了10个线程,每个线程运行5秒钟时间。由于线程的IsBackground属性默认为false,即它们都是前台线程,所以尽管程序的主线程很快就运行结束了,但程序要到所有已启动的线程都运行完毕才会结束。示例代码如下:
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.Start ();
}
}
private static void ThreadFunc ()
{
DateTime start = DateTime.Now;
while ((DateTime.Now - start).Seconds <5)
;
}
}
接下来我们对上面的代码进行略微修改,将每个线程的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少的多线程应用程序,读者需要掌握诸如线程同步、线程池等高级的多线程编程技术。读者不妨参考一些操作系统方面或是多线程编程方面的技术丛书。
http://www.phome.net/document/asp/200504/asp111246384214577.html
评论