.NET上下文(转,.net组件程序设计第二版11.2)

参考文章:

http://developer.51cto.com/art/200805/74151.htm

http://developer.51cto.com/art/200805/74150.htm   

.NET上下文     

     每个新的应用程序域启动时都有一个单一的上下文,我们称为默认上下文。默认上下文根本不提供任何组件服务。它存在的主要原因是因为有助于保持一致的编程模式。在新应用程序域中创建的第一个对象将放置在默认上下文中,即使它不是一个面向上下文对象也如此。这样可保持所有对象都在上下文中执行这样一个设计原则,即使它们不关心组件服务。应用程序域可以包含多个上下文,并且.NET会根据需要创建新的上下文。对应用程序域可以包含的上下文数量没有限制。一个上下文只属于一个应用程序域,它可以承载多个面向上下文对象(见图11-1)。每个上下文都有一个称为上下文ID的唯一I D(整型),在应用程序域范围中它的值是唯一的。每个.NET上下文都有与之关联的一个上下文对象。上下文对象是Context类的一个实例,它在System.Runtime.Remoting.Contexts命名空间中定义。通常不需要与上下文对象进行交互。但为了诊断和跟踪,有时使用上下文对象的只读属性ContextID检索上下文ID比较有用。

public class Context

{

   public virtual int ContextID{get;}

   //其他成员

}

每个对象都可以访问正执行于其中的上下文对象,方法是通过Thread类的Current- Context静态只读属性:

public sealed class Thread

{

   public static Context CurrentContext{ get; }

   /* 其他成员 */

}

例如,下面是对象如何跟踪其上下文ID的方法:

int contextID = Thread.CurrentContext.ContextID;

Trace.WriteLine("Context ID is " + contextID);

请注意,线程能够进入和退出上下文,通常,它们跟任何上下文都没有关联。

将对象分配给上下文(Assigning Objects to Contexts)

     如前所述,有两种.NET类型:上下文敏捷和面向上下文类型。两种类型都始终在某个上下文中执行,主要的区别在于它们与该上下文的关系。上下文敏捷行为是.NET的默认行为。未从ContextBoundObject派生的任何类都是上下文敏捷的。上下文敏捷对象不关心具体组件服务;它们可以在其调用客户端的上下文中执行,因为.NET无须拦截进入它们的调用。当客户端创建上下文敏捷对象时,该对象将在其创建客户端的上下文中执行。客户端对该对象获得一个直接引用,并且不涉及任何代理。客户端可以将对象引用传递到同一上下文或不同上下文中的不同客户端。在其他客户端使用该对象时,对象将在该客户端的上下文中执行。图11-2显示了上下文敏捷模型。请注意:说一个上下文敏捷的对象没有上下文是不正确的。它其实有一个,即进行调用的客户端的上下文。如果上下文敏捷的对象检索其上下文对象,并查询上下文ID的值,它将获得与其调用客户端相同的上下文ID。

图11-2:上下文敏捷的对象

提示:COM+也有上下文敏捷对象的概念,其形式为集成自由线程封送器(FTM)的对象。当一个对象有其他 COM+ 对象作为成员时,FTM 带来了一些很麻烦的副作用,因此要避免这个技术。

     若是面向上下文对象,情况会显著不同。面向上下文对象在整个生命期间都绑定到某个特定的上下文中。具体对象会在哪个上下文中,这是在对象创建时基于其请求的服务及其创建之的客户端的上下文而决定的。如果创建它的客户端的上下文“足够满足”对象的需求,也就是说,上下文具有足够的属性,并且客户端和对象使用一组兼容的组件服务,则将把对象放置在创建它的客户端的上下文中。另一方面,如果对象需要某些其他服务而创建它的客户端上下文不支持的话,则.NET将创建一个新的上下文并在其中放置新的对象。请注意,.NET不会尝试在该应用程序域中查找是否已经有适合于对象的另一个上下文。算法非常简单:对象要么共享其创建者的上下文,要么获得一个新的上下文。此算法有意牺牲内存和上下文管理的成本,以加快将新对象分配到上下文的速度。另一个方法可以先检查可能很长的现有上下文列表中的每一个,但该搜索可能会花费较长时间从而影响性能。如果将对象放置在与创建它的客户端的上下文不同的上下文中,则客户端将从.NET获得一个代理,而不是直接引用(见图11-3)。代理将拦截客户端在对象上的调用,并执行某些调用前置处理和后置处理,以提供对象所需的服务。

图11-3:面向上下文对象的客户端通过代理对其进行访问

提示:将面向上下文对象分配到上下文中的.NET策略与COM+上下文激活策略非常相似。

调用拦截架构(The Call Interception Architecture)

     跨上下文拦截架构与跨应用程序域边界所使用的架构相似。回忆一下第10章,在.NET中代理有两部分:透明代理和真实代理。透明代理有跟对象相同的公共接口。当客户端调用透明代理时,它将把堆栈转换为消息,并将消息传递到真实代理。该消息是实现了 IMessage接口的对象:

public interface IMessage

{   

   IDictionary Properties{ get; }

}

该消息是一个属性集合,如方法名及其参数。真实代理知道实际对象的驻留地点。在跨应用程序域调用的情况下,真实代理需要使用一个格式器来序列化该消息,并将其传递到通道中。在跨上下文调用中,真实代理需要在转发调用给对象之前,实施各种拦截步骤。.NET的良好设计允许.NET在两种情况下使用相同的真实代理。真实代理并不知道格式器、通道或上下文拦截器;它只是将消息传递到消息接收器。消息接收器是实现IMessageSink接口的对象,IMessageSink定义在System.Runtime.Remoting.Messaging命名空间中。

public interface IMessageSink

{

   IMessageSink NextSink{ get; }

   IMessageCtrl AsyncProcessMessage(IMessage msg,IMessageSink replySink);

   IMessage SyncProcessMessage(IMessage msg);

}

.NET将消息接收器排列在一个链表中。每个消息接收器都知道列表中的下一个接收器(你可以通过NextSink属性获取下一个接收器)。真实代理将调用第一个接收器的SyncProcess-Message()方法,让它处理消息。处理完消息后,第一个接收器将调用下一个接收器的 SyncProcessMessage()。在跨应用程序域调用时,客户端上的第一个接收器是消息格式器(再次见图10-9)。在格式化消息后,格式接收器将把它传递到下一个接收器,即传输通道。SyncProcessMessage()方法返回到代理时,它返回对象返回的消息。

提示:IMessageSink接口也提供AsyncProcessMessage()方法,它可以拦截异步调用(此主题超出了本书的范围)。

跨上下文接收器

     在跨上下文调用中,无须格式器;.NET使用一个称为CrossContextChannel的内部通道,它也是一个消息接收器。但是,客户端和对象的组件服务配置之间有一些区别,要由接收器来弥补这些区别。.NET会在客户端上下文和对象之间安装所有需要的消息接收器(见图11-4)。

图11-4:对面向上下文对象的跨上下文调用

.NET上下文拦截架构与装饰(Decorator)设计模式(注1)相似,它是一个面向方面编程的特有情况(将在后面进行讨论,位于补充介绍“上下文、AOP 和 Indigo”中)。典型的消息接收器既执行调用前置处理,也执行调用后置处理。标准的例子就是线程同步。接收器需要在调用对象之前先获得锁,并且它在方法返回之后必须释放该锁。调用链上的下一个接收器可能会实现某种安全机制,等等。

最好用一个示例来说明接收器工作的方式。示例11-1显示了一个通用的接收器实现。接收器构造函数将接受链中的下一个接收器。调用SyncProcessMessage()方法时,接收器会执行某些调用前置处理,然后调用下一个接收器上的SyncProcessMessage()。调用将

沿着接收器链一直下去,直到达到一个堆栈生成器即最后一个接收器为止。堆栈生成器将把消息转换成堆栈框架,并调用对象。当调用返回到堆栈生成器时,它生成一条带有方法结果的返回消息,并将该消息返回到调用它的接收器。然后,该接收器会进行调用后置处理,并将控制权返回给调用它的接收器,等等。最后,调用将返回通用的接收器。现在,通用接收器可以检查返回的消息,并进行一些调用后置处理,然后将控制权返回给调用它的接收器。链中的第一个接收器将把控制权返回给真实代理,为之提供来自对象的返回消息。真实代理将把消息返回给透明代理,透明代理将其返回到调用客户端的堆栈上。

示例11-1:消息接收器的通用实现

public class GenericSink : IMessageSink

{

   IMessageSink m_NextSink;

  

   public GenericSink(IMessageSink nextSink)

   {

      m_NextSink = nextSink;

   }

  

   public IMessageSink NextSink

   {

      get

      {

         return m_NextSink;

      }

   }

   public IMessage SyncProcessMessage(IMessage msg)

   {

      PreCallProcessing(msg);

  

      //此处调用对象:

      IMessage returnedMessage = m_NextSink.SyncProcessMessage(msg);

     

      PostCallProcessing(returnedMessage);

  

      return returnedMessage;

   }

   void PreCallProcessing(IMessage msg)

   {

      /* 进行调用前处理 */

   }

   void PostCallProcessing(IMessage msg)

   {

      /* 进行调用后处理 */

   }

   public IMessageCtrl AsyncProcessMessage(IMessage msg,IMessageSink replySink)

   {

      /* 处理异步调用,然后: */

      return m_NextSink.AsyncProcessMessage(msg,replySink);

   }

}

消息接收器类型

     调用拦截可能会发生在两个地方。第一,接收器可以拦截进入上下文的调用,并进行一些调用前置处理和后置处理,如锁定和解除线程锁。这样的接收器称为服务器端接收器。第二,接收器可以拦截从上下文出去的调用,并进行一些调用前置处理和后置处理。这样的接收器称为客户端接收器。例如,使用客户端接收器,Synchronization属性可以选择跟踪同步域之外的调用,并解除锁定以允许其他线程访问。这是由客户端接收器进行的。你将看到如何安装接收器。

拦截进入上下文的所有调用的服务器端接收器称为服务器上下文接收器。拦截对特定对象的调用的服务器端接收器称为服务器对象接收器。服务器负责安装服务器端接收器。由客户端安装的客户端接收器称为客户端上下文接收器,它们会影响从上下文出去的所有调用。由对象安装的客户端接收器称为特使(envoy)接收器。特使接收器仅拦截与之关联的将特定对象的调用。客户端上的最后一个接收器和服务器端上的第一个接收器都是 CrossContextChannel类型的实例。接收器链是由各段组成的,每个段都是一个不同类型的接收器,如图11-5所示。因为在接收器链结束处必须有一个堆栈生成器才能转换消息,所以.NET在每个段的结束处都安装一个终结器。终结器是该段类型的一个接收器,它会对该段进行最终的处理,并将消息转发到下一段。例如,服务器上下文接收器段中的最后一个消息接收器称为ServerContextTerminatorSink。终结器的行为就像一个真正的死结:如果调用终结器上的IMessageSink.NextSink,则会获得一个空引用。实际的下一个接收器(下一个段中的第一个接收器)是终结器的一个私有成员。结果,你无法使用IMessageSink.NextSink从头到尾遍历整个拦截链。

提示:还有另外一种类型的接收器,称为动态接收器,它将允许在运行时按编程方式添加接收器,而不需要使用属性。动态接收器超出了本书的范围。

相同上下文调用(Same-Context Calls)

     跨上下文边界时必须通过代理来访问面向上下文的对象,以便各种接收器都可以就位拦截调用。现在的问题是,如果与对象在同一上下文的某个客户端将对象的引用传递到另一个上下文中的某个客户端时,会出现什么情况?如果同一上下文的客户端有该对象的直接引

图11-5:客户端和服务器段接收器链

用,那么.NET如何检测这一点,以及如何将代理引入到该对象和新的客户端之间?.NET是这样解决这个问题的,总是通过代理访问对象,即使是相同上下文中的客户端(见图11-6)。因为客户端和对象共享同一上下文,所以消息接收器无需执行任何调用前置处理和后置处理。拦截层由透明代理和真实代理以及一个消息接收器(堆栈生成器)组成。当同一上下文客户端将其对透明代理的引用传递到其他上下文中的客户端时,.NET将检测这一点,并在新客户端和该对象之间设置正确的拦截链。

图11-6:即使在同一上下文中,客户端也使用代理来访问面向上下文对象

提示:在COM+环境中,同一上下文的客户端拥有对象的直接引用。所以,开发人员必须手动地封送这些引用,也就是说,他们必须手动设置代理,并且必须协调这两个客户端间的封送协议。这经常是通过使用全局接口表(Global Interface Table,GIT)完成的。

面向上下文对象和Remoting(Context-Bound Objects and Remoting)

     面向上下文的对象是.NET Remoting的一种特殊情况(为一种客户端激活对象)。在许多方面,.NET都只是将它们作为远程对象进行处理,但.NET确实优化了Remoting架构的某些元素以利于面向上下文对象,例如,像之前提到的,用于跨上下文调用的通道是一个称为CrossContextChannel的经过优化的通道。对于真正的远程客户端激活对象,.NET将创建一个租约,并通过租约及其赞助者管理对象的生命周期。但是,因为面向上下文对象的客户端与其共享同一应用程序域,所以.NET仍然可以使用垃圾收集来管理对象的生命周期。事实上,当.NET创建面向上下文对象时,它仍然为其创建了一个租约,该对象甚至可以替换MarshaByRefObject.InitializeLifetimeService()并提供其自己的租约。但是,该租约不能控制对象的生命周期。

提示:对于真正的远程对象,当TCP或HTTP通道跨进程序边界封送一个引用时,这些通道将设置租约。对于跨上下文对象或跨应用程序域对象,CrossContextChannel和CrossAppDomain- Channel通道会忽略该对象的租约,而是让垃圾收集来管理远程对象。

posted @ 2008-09-25 11:23  bluealarm  阅读(437)  评论(0编辑  收藏  举报