代码改变世界

static成员与Asp.net中Application对象的线程同步实现

2008-11-03 16:41  GUO Xingwang  阅读(2540)  评论(5编辑  收藏  举报

     在做.net平台的应用开发时经常遇到线程同步的问题,在多线程环境下对共享资源的访问我们有lock关键字来实现线程同步。lock关键字实际上也就是Monitor.Enter和Moniter.Exit的语言级别上的实现,本质上没有区别。那么Monitor.Enter和Moniter.Exit在底层上又是怎么实现的呢?Asp.net的Appliction对象(以下直接称为Appliction)又是怎样封装的呢?

     第一个问题我不想再多说了,因为Anytao已经在[你必须知道的.NET]第十九回:对象创建始末(下)里讲的很清楚,如果您想更深入的学习可以看一下那篇文章后面的参考文献部分。我个人理解就是在Clr的托管堆中为对象留出一个特殊的存储空间来标识该对象是否被线程占有来实现,甚至Win32也是这样的实现。

     第二个问题是个很有意思的问题,这也是本文所关注的问题。

     在Asp.net中对于应用程序级的共享资源的访问我们经常会使用static和Appliction对象.

"使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员。static 修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型。例如,下面的类声明为 static,并且只包含 static 方法。"这是MSDN对于static的作用描述。

如果static修饰的成员为public的,我们可以通过类名。成员名的方式进行访问,static成员在应用程序中只有一份拷贝,这也就涉及到了线程同步问题.如果在页面中有对 static 变量的写入操作,应该在代码中进行lock ,或者使用原子操作 Interlocked 类。

Application是HttpApplicationState的一个实例,这个实例在一个应用程序中只存在一个,也就是我们常说的单例。无论使用static还是单例模式都可以实现在应用程序内的唯一,我更喜欢使用单例模式来实现唯一,尤其是结构比较复杂的类的处理,因为我觉得它更灵活些。下面是MSDN对于HttpApplicationState的描述:"ASP.NET 应用程序是单个 Web 服务器上的某个虚拟目录及其子目录范围内的所有文件、页、处理程序、模块和代码的总和。HttpApplicationState 类的单个实例在客户端第一次从某个特定的 ASP.NET 应用程序虚拟目录中请求任何 URL 资源时创建。对于 Web 服务器上的每个 ASP.NET 应用程序都要创建一个单独的实例。然后通过内部 Application 对象公开对每个实例的引用。此类型是线程安全的。"

     既然是线程安全的,那么.Net Framework是如何做到线程安全的呢?查看一下源代码文末链接处给出,我们发现HttpApplicationState又继承自NameObjectCollectionBase,可知道其内部存储应该是通过集合对象完成的,值得关注的是HttpApplicationState对于集合内部的访问每次都是用了_lock.AcquireWrite();等,_lock是一个HttpApplicationStateLock,HttpApplicationStateLock继承自ReadWriteObjectLock,这个东西很关键。可以说Application对象的线程同步问题都是围绕着ReadWriteObjectLock来展开的,不过ReadWriteObjectLock内部还是要使用lock关键字,这就回到了原点。ReadWriteObjectLock源代码如下:

ReadWriteObjectLock

     

     由此可知在HttpApplicationState 类(Application 对象是 HttpApplicationState 类的一个实例)内部是使用一个 ReadWriteObjectLock 对象来操作的,在读取进行时计数加 1,读取完成时计数减 1,写入是排他的,要等到计数为零才能开始写入,写入过程中会将计数置为 -1,防止其他线程进入读取。

     但是,通常我们只对对象的写操作进行线程同步,为什么微软在实现Application的访问时会这样复杂呢,假如有Application["A"]、Application["B"]、Application["C"],有线程访问Application["A"]其他线程不能访问Application["B"] and Application["C"]。一般只要对写操作进行线程同步不就行了吗,看代码我们可知Application在读写等操作时都进行了加锁和解锁.仔细想想分析原因可能如下:
1.Application是运行在多线程的环境下,可能同时有多个线程同时对它进行读写等操作,所以肯定要进行lock,那么为什么要这么lock呢,请看2
2.Application内部是通过集合对象实现的,如果我们lock整个Application那么挂起的线程就会很多(Asp.net的请求很多啊)这样势必会给应用程序带来很大的性能问题.如果lock集合中的一个对象的话也会有一个问题,那就是如果集合中的一个对象在引用集合中的另一个对象(这两个对象不是线程安全的)时应用程序状态不在网络场(应用程序被多台服务器承载)或网络园(应用程序被同一台计算机上的多个进程承载)中共享。

     如果使用Application对象,仅在 Application 集合中的两个或多个对象之间关系密切的时候,才需要使用lock(Application) 或者Application.Lock() ,其他情况下几乎不需要考虑线程同步的问题。

     这样看来,对于一般的操作,Application 对象就足够了。static 成员则更有利于类的逻辑封装和重用。

     举个static 成员使用的例子:一个 WebForm 程序和一个 WinForm 程序,要用到同一份数据,同样需要用到缓存技术,可以使用static成员将逻辑封装为一个class,这个 class 就可以同时用在WebForm和WinForm当中。

     当然我们对于自己的应用也可以使用ReadWriteObjectLock类像HttpApplicationState那样做线程同步。

     有时候多看看.Net的源代码并做一些查找和研究对于丰富自己设计与开发思路还是很有帮助的。

     src方便下载:httpapplicationstate     ReadWriteObjectLock