操作系统使用进程将它们正在执行的不同应用程序分开。线程是操作系统分配处理器时间的基本单元,每个进程中都可以有多个线程同时执行代码。每个线程都维护异常处理程序、调度优先级和线程上下文结构。在调度线程之前,系统将线程上下文保存起来,以便在下一次执行时能够从保存的状态重新开始。线程上下文包括为使线程在线程的宿主进程地址空间中无缝地继续执行所需的所有信息,包括线程的CPU寄存器组和堆栈。
应用程序域(由System.AppDomain类实现)是公共语言运行库为应用程序提供隔离的单元。它们在进程中创建和运行。应用程序域通常由运行时宿主创建,运行时宿主是负责将运行时载入进程,并在应用程序域中执行用户代码的应用程序。运行时宿主创建进程和默认应用程序域,并在其中运行托管代码。运行时宿主包括ASP.NET、Microsoft Internet Explorer和Windows外壳程序。
.NET框架中的线程是一种由应用程序域管理的轻量托管子进程,由System.Threading. Thread类实现。一个或多个托管线程可以在同一个非托管进程中的一个或多个应用程序域中运行。虽然每个应用程序域都是用单个线程启动的,但该应用程序域中的代码可以创建附加应用程序域和附加线程。
在支持抢先多任务处理的操作系统中,多个进程中的多个线程可以同时执行。操作系统将处理器时间划分为时间片,并轮流为每个线程分配处理器时间片。当前执行的线程在其时间片结束时被挂起,而另一个线程继续运行。当系统从一个线程切换到另一个线程时,它将保存被抢先的线程的线程上下文,并重新加载线程队列中下一个线程的(已保存)线程上下文。
时间片的长度取决于操作系统和处理器。由于每个时间片都很小,因此即使只有一个处理器,多个线程看起来似乎也是在同时执行。如果系统存在多个处理器,线程就会分布在多个可用处理器中执行。
与用户进行交互的软件不但必须尽可能快地对用户的活动做出反应,而且还必须执行必要的计算,以便尽可能快地将数据呈现给用户。使用多个线程提供了一种很好的解决方法。在具有一个处理器的计算机上,多个线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。多线程程序在具有多个处理器的计算机上运行时,将极大地提高程序的性能,满足用户的需要。
单个应用程序域可以使用多个线程来完成以下任务:
— 通过网络与Web服务器和数据库进行通信。
— 执行占用大量时间的操作。
— 区分具有不同优先级的任务。例如,高优先级线程管理时间紧迫的任务,低优先级线程执行其他任务。
— 使用户界面可以在将时间分配给后台任务时仍能快速做出响应。
但是,多线程并不是万能的。最好是仅仅将它应用于必要的情形,应该使用尽可能少的线程,以最大限度地减少操作系统资源的使用和提高系统性能。多线程处理需要考虑如下的资源需求:
— 系统将为进程、AppDomain 对象和线程所需的上下文信息使用内存。因此,可以创建的进程、AppDomain 对象和线程的数目会受到可用内存的限制。
— 跟踪大量的线程将占用大量的处理器时间。如果线程过多,则其中大多数线程都不会产生明显的进度。如果大多数当前线程处于一个进程中,则其他进程中的线程的调度频率就会很低。
— 使用许多线程来控制代码时,执行会变得非常复杂,并可能产生许多缺陷。
— 销毁线程需要了解可能发生的问题,还必须处理这些问题。
提供对资源的共享访问会引起冲突。为了避免冲突,必须对共享资源的线程进行同步访问,或者控制对共享资源的访问。如果在相同或者不同的应用程序域中未能正确地同步访问共享资源,就可能会导致出现一些问题,包括死锁(deadlock)和竞争条件(race conditions)等。死锁是指两个线程都停止响应,并且都在等待对方释放自己需要的资源;竞争条件是指由于意外地出现对两个线程的执行时间的临界依赖性而导致反常的结果。
系统提供了可用于协调多个线程之间的资源共享的同步对象。减少线程的数目可以简化资源的同步处理。使用多线程时,需要同步的资源包括:
— 系统资源(如通信端口)。
— 多个进程所共享的资源(如文件句柄)。
— 由多个线程访问的单个应用程序域的资源(如全局、静态和实例字段)。
一般地,如果面临下列情形之一,就需要考虑使用线程:
— 需要使一个任务具有特定的优先级。
— 具有长时间运行(并因此阻塞其他任务)的任务。
— 需要将线程放置到单线程单元中。
— 需要与该线程相关的稳定标识。
— 需要运行与用户界面交互的后台线程。
System.Threading命名空间中定义了与多线程处理相关的类型。其中的核心是Thread类,它用于创建和控制线程、设置其优先级、获取和设置其状态。表19-1列出了Thread类的主要成员。
表19-1 System.Threading.Thread类的方法和属性
成 员 |
说 明 | |
属性 |
ApartmentState |
获取或设置此线程的单元状态 |
CurrentContext |
获取线程正在其中执行的当前上下文 | |
CurrentCulture |
获取或设置当前线程的区域性 | |
CurrentPrincipal |
获取或设置线程的当前负责人(对基于角色的安全性而言) | |
CurrentThread |
获取当前正在运行的线程 | |
CurrentUICulture |
获取或设置资源管理器使用的当前区域性,以便在运行时查找区域性特定的资源 | |
ExecutionContext |
获取一个ExecutionContext对象,该对象包含有关当前线程的各种上下文的信息 | |
IsAlive |
获取一个值,该值指示当前线程的执行状态 | |
IsBackground |
获取或设置一个值,该值指示某个线程是否为后台线程 | |
IsThreadPoolThread |
获取一个值,该值指示线程是否属于托管线程池 | |
ManagedThreadId |
获取当前托管线程的唯一标识符 | |
Name |
获取或设置线程的名称 | |
Priority |
获取或设置一个值,该值指示线程的调度优先级 | |
ThreadState |
获取一个值,该值包含当前线程的状态 | |
方法 |
Abort |
已重载。在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程 |
BeginCriticalRegion |
通知宿主执行将要进入一个代码区域,在该代码区域内线程终止或未处理的异常的影响可能会危害应用程序域中的其他任务 | |
BeginThreadAffinity |
通知宿主托管代码将要执行依赖于当前物理操作系统线程的标识的指令 | |
EndCriticalRegion |
通知宿主执行将要进入一个代码区域,在该代码区域内线程终止或未处理的异常仅影响当前任务 | |
EndThreadAffinity |
通知宿主托管代码已执行完依赖于当前物理操作系统线程的标识的指令 | |
GetApartmentState |
返回一个ApartmentState值,该值指示单元状态 | |
GetCompressedStack |
返回一个CompressedStack对象,该对象可用于捕获当前线程的堆栈 | |
GetData |
在当前线程的当前域中从当前线程上指定的槽中检索值 | |
GetDomain |
返回当前线程正在其中运行的当前域 | |
GetDomainID |
返回唯一的应用程序域标识符 | |
Interrupt |
中断处于WaitSleepJoin线程状态的线程 | |
Join |
已重载。阻塞调用线程,直到某个线程终止时为止 | |
MemoryBarrier |
同步内存。其效果是将缓存内存中的内容刷新到主内存中,从而使处理器能执行当前线程 | |
ResetAbort |
取消为当前线程请求的Abort | |
Resume |
继续已挂起的线程 | |
SetApartmentState |
在线程启动前设置其单元状态 | |
SetCompressedStack |
对当前线程应用捕获的CompressedStack |
续表
成 员 |
说 明 | |
方法 |
SetData |
在当前正在运行的线程上为此线程的当前域在指定槽中设置数据 |
Sleep |
已重载。将当前线程阻塞指定的毫秒数 | |
SpinWait |
导致线程等待由iterations参数定义的时间量 | |
Start |
已重载。使线程得以按计划执行 | |
Suspend |
挂起线程,或者如果线程已挂起,则不起作用 | |
TrySetApartmentState |
在线程启动前设置其单元状态 | |
VolatileRead |
已重载。读取字段值。无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值 | |
VolatileWrite |
已重载。立即向字段写入一个值,以使该值对计算机中的所有处理器都可见 |
在创建操作系统进程时,操作系统将插入一个线程以执行该进程(包括任何原始应用程序域)中的代码。如果正在执行的代码是托管代码,则可以通过检索Thread类的静态CurrentThread属性来获取正在当前应用程序域中执行的线程的Thread对象。
Thread类的静态方法Sleep用于使调用线程休眠指定的毫秒数。经常使用Thread类的实例方法Join来协调线程。在一个线程A中调用另一个线程B(Thread对象)的Join方法将阻塞线程A,直到线程B结束为止。这是协调线程之间的执行步骤的最简单方法。
一个进程可以创建一个或多个线程,以执行与该进程关联的部分程序代码。创建线程时,使用ThreadStart委托或ParameterizedThreadStart委托指定由线程执行的程序代码。使用ParameterizedThreadStart委托可以将数据传递到线程过程。
在线程存在期间,它总是处于由ThreadState定义的一个或多个状态中。可以为线程请求由ThreadPriority定义的调度优先级,但不能保证操作系统会接受该优先级