.NET 框架与多线程袁剑 (转贴)


.NET 框架与多线程
本文发表在《程序春秋》2003年12期
摘要
本文介绍了.NET框架的基本概念并简要的描述了如何利用.NET框架编写多线程程序。
目录
 概述
 功能特性
 编程模型
 线程概述
 线程控制
 线程同步
概述
Microsoft.NET代表了一个集合、一个环境、一个可以作为平台支持下一代Internet的可编程结构,它通过使用HTTP,XML等标准,使得各个系统平台间互操作性成为现实,Microsoft.NET最重要部分是.NET框架,.NET框架是一种新的计算平台,它简化了在高度分布式Internet环境中的应用程序开发,它支持超过20种不同的编程语言,它帮助开发人员把精力集中在实现商业逻辑的核心上,使开发人员的经验在面对类型大不相同的应用程序(如基于 Windows 的应用程序和基于 Web 的应用程序)时保持一致,在未来的版本中甚至仅仅在程序发布时才需要指定发布的类型(作为Win Form还是Web Form)。
如上图,.NET框架由2部分组成:公共语言运行时(CLR:是.NET开发人员的源代码和硬件底层之间的 中间媒介,所有的.NET 代码都在CLR中运行)和框架类库(FCL:包括数据访问组件、基础类库以及WebForm、WinForm、Web Services模板)。
功能特性
CLR和FCL紧密结合在一起,提供了不同系统之间交叉与综合的解决方案与服务,创造了可控的、安全的、功能丰富的应用开发环境,以下是它们提供的部分功能。
一次运行,总能运行
所有的开发人员几乎都知道“Dll Hell”版本控制问题(当新安装的应用程序的组件覆盖旧有应用程序的组件时,导致旧有程序无法正常运行)。为了彻底消除“DLL Hell”现象,.NET框架的结构已与应用程序组件隔离,应用程序运行时必须加载生成时所用到的组件以确保应用程序总是能正常运行。
简化部署
在.NET框架里软件安装的方式是XCOPY,如同在DOS下一样,只需要将应用程序复制到某个目录就完成了安装,卸载应用程序只需要删除目录。
自动内存管理
对于托管资源您再也不必在类里写析构函数(Finalize)进行清理,垃圾管理(GC)可以自动跟踪资源使用,确保不会产生资源泄漏,实际上GC对Finalize的调用时间和次序是不确定的。但是微软建议用户一定要记得在Dispose 方法里释放非托管资源如数据库连接、文件等。
类型安全
每个对象都是单根继承自Object(注:也有人认为是继承自Iobject接口)以确保在运行时可以通过调用GetType方法可以确定对象的类型,通常强制转换为基类型被认为是安全的隐式转换。CLR可以验证所有代码是否类型安全,类型安全能确保总是以兼容的方式访问被分配对象。
编程模型
以前在Windows平台下进行软件开发的C++开发人员大多使用的是微软基础类库(MFC)或者Win32 APIs,Visual Basic用户使用的是Visual Basic API,Delphi用户使用的是Borland公司的VCL,Java用户使用JDK,彼此之间很难兼容、相互调用,软件开发人员有时也难以取舍,而.NET框架统一了当前各种不同的架构,.NET框架为开发人员提供了一个统一的、面向对象的、层次化的、可扩展的框架类库――FCL,开发人员不再需要学习多种架构只须学习.NET框架就能灵活的采用各种不同的编程语言进行开发,.NET框架还实现了跨语言的继承、错误处理、调试。
在.NET框架里从C#,VB.NET,J#到C++的所有编程语言都是相互平等的,开发人员可以自由选择自己喜欢的语言,如下不同语言的程序代码:
C#:
class Hello
{
	static void Main()//程序的出口点
	{
		System.Console.Write( "Hello, World!" );//输出Hello, World!
	}
}
VB.NET:
Module Hello
    Sub Main() '程序的出口点
        System.Console.Write( "Hello, World!" ) '输出Hello, World!
    End Sub
End Module
在编译器上最终生成的都是如下的IL(中间语言)代码:
IL_0000:  ldstr      "Hello, World!"
IL_0005:  call       void [mscorlib]System.Console::Write(string)
IL_000a:  ret
IL是CLR唯一理解的编程语言,所有的编译器都将源代码编译成IL,IL再被CLR处理。
输出的结果是非常熟悉的:Hello, World!。
在.NET框架下您不仅可以开发传统的基于Windows平台下的应用,您还可以开发基于Smart Phone、Pocket PC、Tablet PC等等平台下的应用。
.NET框架提供了强有力的编程模板,可以随心所欲的编写WinForm、WebForm、WebService等类型的程序,这些类型的程序的开发方式与以往有很大的不同,功能也比以往大大加强。其中“最有可能改变世界”的(注:Jeff Prosise语),最令人惊奇的应该是Web Form(ASP.NET)。多线程、GDI+、设计模式、Cache、异常等等这些在以前的Web程序里不敢想象或者很难实现的功能在.NET框架下变得如此的垂手可得。
ASP.NET几乎天生就是基于MVC模式的,它利用Code-Behind技术轻而易举的把页面(View)和代码(Model && Control)分离,再加上Page Controller,Distributed,Services等模式,可以把程序结构从传统的3层扩充到4层、5层甚至更多层,充分满足开发大型企业级应用的需要(以后有机会将向大家展示ASP.NET结合模式开发令人激动的一面)。利用多线程和GDI+技术可以一边显示文字内容、一边从远程抓取图片并把图片变换为适当的形式(如转换为PNG格式,在图片上记录版权信息)显示给用户,效率比以往大大提高。
对于相当多的人来说,开发多线程程序是一个相当大的挑战,但.NET框架已对其进行了相当程度的简化,本文的后面部分将向您介绍多线程编程的基础知识,并提供一些用于试验的示例代码。相信您在看了之后会迅速掌握。
为了便于讲解,示例代码采用的是控制台程序。
线程概述
线程是CPU时间片分配的单位,不同的线程可以具有不同的优先级,每个进程至少会有一个主线程。在.NET框架中最基本的执行单元是线程,托管代码是以单线程开始执行的,但在执行期间可以产生附加的线程来帮助完成指定的任务。线程分为前台线程(所有前台线程都必须退出之后,当前进程才能退出)和后台线程(进程随时可以退出,不等待后台线程退出)。
对于耗时非常长而又不想堵塞界面的操作、服务器端的应用、同时支持多用户并发的环境或者发挥多处理器的作用的时候都应该考虑使用多线程,但是使用线程需要非常谨慎地分析同步情况和线程调度本身的开销。
线程控制
线程控制包括线程启动、挂起、恢复、休眠、终止等,在下面的这个列子里将会对线程控制做一个大体描述。
public class MyThread 
{
	public static void Work() 
	{
		try 
		{
			for( int i = 0; i < 4; i++ ) 
			{
				Console.WriteLine( Thread.CurrentThread.Name + "正在计算" + i.ToString() );
				Thread.Sleep( 50 );
			}
		}
		catch( ThreadAbortException e ) 
		{
			Console.WriteLine( "错误信息:{0}", e.Message );
			Thread.ResetAbort();//取消主线程要求中止的请求,如不执行则ThreadAbortException会在catch块末端再次引发
		}
		Console.WriteLine( Thread.CurrentThread.Name + "完成工作!" );
	}
}

class ThreadAbortTest
{
	public static void Main() 
	{
		Console.WriteLine( "主线程启动!");
		Thread thread = new Thread( new ThreadStart( MyThread.Work ) );//MyThread.Work代表要执行的线程函数,它可以是静态的,也可以是某个类实例的方法,它是要执行的线程函数的委托
		thread.Name = "MyThread";
		thread.Start();//线程启动后将被CPU调度,但不一定是马上执行,所以同样的例子在不同的机器上可能会有不同的结果。
		Console.WriteLine( "主线程第一次休眠!");
		Thread.Sleep( 100 );
		Console.WriteLine( "MyThread被挂起!");
		thread.Suspend();//挂起子线程
		Console.WriteLine( "主线程第二次休眠!");
		Thread.Sleep( 100 );
		Console.WriteLine( "MyThread被恢复!");
		thread.Resume();//恢复子线程
		thread.Abort();//终止子线程
		thread.Join();//直到到子线程完全终止了,主线程才继续运行
		Console.WriteLine( "主线程终止!" );
		Console.ReadLine();
	}
}
程序启动后会创建一个名为MyThread的辅助线程并通过调用thread.Start启动它,然后调用Thread.Sleep( 100 )休眠100微妙,这时辅助线程一直在运行,接下来主线程调用thread.Suspend挂起辅助线程并再休眠100微妙后再调用Thread.Sleep恢复辅助线程,在被挂起和被恢复这段时间内辅助线程被阻塞,紧跟着主线程执行thread.Abort方法终止辅助线程并执行thread.Join等待辅助线程结束,在辅助线程接收到终止请求时它已经工作并又执行了一次循环,辅助线程捕捉到主线程要求终止的异常,执行Thread.ResetAbort退出循环以便执行清理工作。从上面的例子可以清晰的看出控制并发线程整个的过程。运行结果如下图2所示:
线程同步
启动线程很容易,但是让他们协调工作却很难,设计一个多线程的程序时最难的就是计算出并发的线程会在哪里冲突并用同步逻辑来阻止冲突的发生。
.NET框架提供了下面的几个线程同步类:
 AutoResetEvent:阻塞线程直到另一个线程设置事件
 Interlocked:以线程安全的方式进行简单操作、如递增或递减整数
 ManualResetEvent:阻塞一个或多个线程直到另一个线程设置事件
 Monitor:防止一个以上的线程同时访问一个资源
 Mutex:防止一个或多个以上的线程同时访问一个资源,并能跨越应用程序或进程的边界
 ReaderWriterLock:使多个线程可以同时读取一个资源,但不允许重叠的读写或写操作
在以上几个类里面我们用得最多是Monitor,下面将重点对Monitor类进行讲解:
Monitor的几个重要的方法:
 Enter:在访问资源前调用并在该资源上声明一个锁以判断是否该资源被其他资源所占用,如果是则该线程被阻塞直到可以获得该资源为止。
 Exit:在访问完该资源后调用以释放对资源的锁便于其他线程访问。
 Wait:暂时放弃调用线程对该资源的锁。
 Pluse:通知在Wait中阻塞的线程,使得下一个等待线程在当前线程释放锁后可以运行。
在C#里有lock关键字以取代Monitor的Enter和Exit,在功能上
lock ( SomeResource )
{
	//您的处理代码
}
等价于:
Monitor.Enter( SomeResource );
try
{
	//您的处理代码
}
finally
{
	Monitor.Exit( );
}
下面的例子向您具体演示一下怎样用Monitor实现生产者-消费者的问题。表面上看来每个时刻只有一个方法可以执行,但是它们却是并发执行的。首先是Producer得到了锁,他通过调用Monitor.Wait阻塞自身,这时Consumer得到了锁,Consumer调用Monitor.Pulse使得Producer处于准备执行状态,然后Consumer调用Monitor.Wait阻塞自身,这时Producer从Wait调用中醒来,生成一个资源放入队列中,调用Monitor.Pulse使得Consumer从Wait调用中醒来,Consumer从队列中取得资源,接下来Consumer通过调用Monitor.Pulse和Monitor.Wait使得整个进程继续下去。
class MonitorSample
{
	private Queue _queue = new Queue();
	public void Producer()
	{
		int counter = 0;
		lock( _queue )//判断该资源是否被其他线程占用
		{
			while( counter < 2 )
			{
				Monitor.Wait( _queue );//暂时放弃调用线程对该资源的锁,让Consumer执行
				_queue.Enqueue( counter );//生成一个资源
				Console.WriteLine( String.Format( "生产:{0}", counter ) );
				Monitor.Pulse( _queue );//通知在Wait中阻塞的Consumer线程即将执行
				counter++;
			}
		}
	}
	public void Consumer()
	{
		lock( _queue )
		{
			Monitor.Pulse( _queue );//通知在Wait中阻塞的Producer线程即将执行
			while( Monitor.Wait( _queue, 10 ) )
			{
				int counter = ( int ) _queue.Dequeue();//取出一个资源
				Console.WriteLine( String.Format( "消费:{0}", counter ) );
				Monitor.Pulse( _queue );//通知在Wait中阻塞的Producer线程即将执行
			}
		}
	}
	static void Main(string[] args)
	{
		MonitorSample monitor = new MonitorSample();            
		Thread producer = new Thread( new ThreadStart( monitor.Producer ) );
		Thread consumer = new Thread( new ThreadStart( monitor.Consumer ) );
		producer.Start();
		consumer.Start();
		producer.Join();
		consumer.Join();            
		Console.ReadLine();
	}
}
小结
本文仅仅只是展示了.NET框架强大功能的冰山一角,通过两个实例具体讲解了在.NET框架下开发多线程程序的必备知识,当您熟悉以后应该也会认为编写多线程程序是.NET框架里最有趣和有用的东西。
参考资源
Microsoft Developer Network http://msdn.microsoft.com
Microsoft .NET 程序设计技术内幕 Jeff Prosise,王铁等译,清华大学出版社
袁剑
Microsoft ASP.NET MVP
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值