Artech

Develop every application as an art using the most suitable technologies!

常用链接

统计

积分与排名

网上邻居

我的博文系列

最新评论

[原创].NET Framework:用Coding证明Application Domain的隔离性

Application Domain可以看作是一个Assembly的逻辑容器。在程序执行过程中,如果遇到需要的Type并没有定义在已经加载的Assemblies中,CLR会把相应的Assembly加载的该Application Domain中。每个Application Domain都有一个属于自己的加载器堆(Loader Heap),用于维护从Application Domain创建以后所用到的所有的Type,以及这些Type对应的方法表——维护这样一个Mapping:定义在Type中的所有方法和经过JIT编译后x86代码(只考虑32bit处理器)。

Application Domain之间是相互隔离,互不干扰。在一个Application Domain创建的对象不能被另一个Application Domain直接调用,反映在内存分配上面——就是各个Application Domain使用各个独立的内存地址空间。一个对象根据他所对应的类型(如System.MarshalByRefObject通过传递引用的方式)或者属性(比如对于定义了System. SerializableAttribute的Type采用传递值得方式)以两种不同的方式在Application Domain之间传递——By Reference 和By Value。

这些都是地球人都知道的.NET的基本原理,但是相信很多人没有尝试过通过Coding的方式证明这种机制。

那么现在我们就先来看看我们的Sample: 


下面是运行结果的Screen Shot。


接下来我们来分析这段代码。

1. 我们首先定义个3个不同的Type,他们都一个相同的方法——GetAppDomain()用于获取执行该方法的真正的Application Domain。

  • GeneralType:一般的Type, 没有什么特别。
  • MarshalByValueType:定义了一个System. SerializableAttribute(你也可以通过使它实现System.Runtime.Serialization.ISerializable Interface来模拟这个Sample),它将以By Value的方式在不同的Application Domain之间传递。
  • MarshalByRefType:继承自System. MarshalByRefObject。该类型的对象它将以By Reference的方式在不同的Application Domain之间传递。

public class GeneralType
    
{
        AppDomain GetAppDomain()
        
{
            
return AppDomain.CurrentDomain;
        }

    }


    [Serializable]
    
public class MarshalByValueType
    
{
        
public AppDomain GetAppDomain()
        
{
            
return AppDomain.CurrentDomain;
        }

    }


    
public class MarshalByRefType : MarshalByRefObject
    
{
        
public AppDomain GetAppDomain()
        
{
            
return AppDomain.CurrentDomain;
        }

 }

2. 在Main()中我们首先创建一个新的Application Domain,并为他指定一个Friendly Name——newAppDomain。

AppDomain appDomain = AppDomain.CreateDomain("newAppDomain");

3. 接着我们分别在这个新建的Appliation Domain中创建我们在1中定义的3个类型的对象——generalObject,marshalByValueObject,marshalByRefObject,并试着把它传递到当前的Application Domain——Default Application Domain。从运行的结果我们可以看出,当我们传递generalObject的时候,一个Exception被抛出,从Error Message可以开出原因——“Type 'Artech.AppDomainIsolation.GeneralType' in assembly 'Artech.AppDomainIsolation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.”从而我们可以看出,一般的Type是不能在不同的Application之间传递的。

try
            
{
                GeneralType generalObject 
= (GeneralType)appDomain.CreateInstanceAndUnwrap(assemblyName, "Artech.AppDomainIsolation.GeneralType");
            }

            
catch (Exception ex)
            
{
                Console.WriteLine(
"Fail to pass a general type instance created in another application domain to default appliction domain,beacuse \"{0}\"", ex.Message);
            }


            MarshalByValueType marshalByValueObject 
= (MarshalByValueType)appDomain.CreateInstanceAndUnwrap(assemblyName, "Artech.AppDomainIsolation.MarshalByValueType");
            MarshalByRefType marshalByRefObject 
= (MarshalByRefType)appDomain.CreateInstanceAndUnwrap(assemblyName, "Artech.AppDomainIsolation.MarshalByRefType");

4. 通过3,证明了标记的System. SerializableAttribute属性和继承自System. MarshalByRefObject的Type对应的对象是可以在不同的Application Domain之间传递呢。但是他们之间又会有怎样的差异呢?他们是真正的对象呢?还仅仅是位于新建Application Domain中的对象一个代理(Proxy)?我们通过调用定义在System.Runtime.Remoting中的静态方法: IsTransparentProxy。了解Remoting的人相信对这个方法不会感到陌生。他用于判断某个对象是否是一个Transparent Proxy(在当我们跨Application Domain远程调用一个Remote Object的时候,实际上我们并非直接调用Remote Object的方法,而是通过一个同Client处在同一个Application Domain的Transparent Proxy对象间接地调用远程对象——Transparent Proxy具有一个Remote Object的Reference,可以轻易地找到这个Remote Object。其实在真正的场景中,Client调用Transparent Proxy,Transparent Proxy再去调用Real Proxy,Real Proxy最终才去调用Remote Object——如果你想进一步地了解Remoting,你可以参照MSDN)

Console.WriteLine("\nmarshalByValueObject is a transparent proxy? {0}.",RemotingServices.IsTransparentProxy(marshalByValueObject)?"Yes":"No");
            Console.WriteLine(
"marshalByRefObject is a transparent proxy? {0}.", RemotingServices.IsTransparentProxy(marshalByRefObject) ? "Yes" : "No");

通过运行结果,我们可以看到:MarshalByValueType对象marshalByValueObject,IsTransparentProxy方法返回False。而对于MarshalByRefType对象marshalByRefObject则返回True。这就充分证明了,标记了System. SerializableAttribute属性的Type所对应的对象是一个真正意义上的对象,而对于继承自MarshalByRefObject Type,当该Type对应的对象从一个Application Domain传递到另一个Application Domain后,另一个Application Domain获得的仅仅是原来对象的Proxy而已。

我们可以从传递的机制来解释这种差异。当你把System. SerializableAttribute属性运用要某个Type,或者让某个Class实现System.Runtime.Serialization.ISerializable Interface的时候,你实际上是给该Type赋予了一种能力——一种可以序列化成XML的能力(XMLSerializer负责把对象序列化成XML)。当这种对象从一个Application Domain传递到另一个Application Domain的时候,Object先被序列化成XML,接着把XML传递到另一个Application Domain中,在新的Application Domain中,通过反序列化重新生成一个新的Object——这个新的Object和处于另一个Application Domain已经没有任何关系。

而对于而对于继承自MarshalByRefObject Type的对象,虽然他不能够序列化成XML,但是可以通过传递Reference的方式在Application Domain之间 传递。当这种传递实质上是通过在另一个Application创建一个拥有该对象引用的一个Proxy。而这个Proxy依赖一这个处理另一个Application Domain的真正对象。

5. 上面我们实际上已经说清楚了两个对象传递的差异——By Value 和By Reference。现在我们来进一步验证前面我们说的。我们继续来看看我们的代码:

AppDomain appDomain_MarshalByValueType = marshalByValueObject.GetAppDomain();
AppDomain appDomain_MarshalByRefType 
= marshalByRefObject.GetAppDomain();

            Console.WriteLine(
"\nThe method of marshalByValueObject is executed in the default application domain?\"{0}\"",
                
object.ReferenceEquals(AppDomain.CurrentDomain, appDomain_MarshalByValueType) ? "Yes" : "No");
            Console.WriteLine(
"The method of marshalByRefObject is executed in the default application domain?\"{0}\"",
               
object.ReferenceEquals(AppDomain.CurrentDomain, appDomain_MarshalByRefType) ? "Yes" : "No");

我们通过调用连个对象的GetAppDomain()方法,从而获得真正执行该方法的Application Domain。然后再和当前的Application Domain进行比较。我们发现对于MarshalByValueType对象marshalByValueObject,真正执行操作是在当前的Application Domain中进行的。而对于MarshalByRefType对象marshalByRefObject,则是我们新建立的Application Domain。这充分证明了当marshalByValueObject传递到新的Application后,生成一个和原来对象一模一样的对象,这个对象具有执行自身操作的能力。而对于marshalByRefObject,由于它只是一个Proxy而已,他只有把对对象的操作请求发送给真正的,位于另一个Application Domain的真正对象(同时他也负责把远程对象的执行结果返回给调用者),真正的操作实际上是发生在远程对象的Application Domain。

由于marshalByRefObject,依赖于远程对象,所以当我们卸载掉Host远程对象的Application Domain,对marshalByRefObject的任何调用将变得无效。所以这里可以对下面这段代码作出解释。

AppDomain.Unload(appDomain_MarshalByRefType);
marshalByValueObject.GetAppDomain();

            
try
            
{
                marshalByRefObject.GetAppDomain();
            }

            
catch (Exception ex)
            
{
                Console.WriteLine(
"\nAn exception is thorwn when calling the method of marshalByRefObject because \"{0}\"", ex.Message);
            }

注:在分布式开发中,我们会大量接触到By Value 和By Refernce传递。一般而言,By Value用于商业实体的传递(Business Entity),而By Reference用于远程调用(RPC)或者是调用Service。

posted on 2007-03-05 23:58 Artech 阅读(13280) 评论(36)  编辑 收藏 网摘 所属分类: A. .NET Framework

评论

#1楼 2007-03-06 01:18 Jeffrey Zhao      

长进了!您的文章都非常精彩啊!   回复  引用  查看    

#2楼 2007-03-06 08:18 nmacro[未注册用户]

写得不错,非常清楚。
不过我觉得序列化时未必用的是XML方式,序列化成内存块的可能性大些
  回复  引用    

#3楼[楼主] 2007-03-06 09:22 Artech      

@nmacro
应该是象你说的,等我确定了之后给你一个答复。
  回复  引用  查看    

#4楼 2007-03-06 09:24 Anders.Zhao      

"GeneralType:一般的Type, 没有什么特别。

MarshalByValueType:定义了一个System. SerializableAttribute(你也可以通过使它实现System.Runtime.Serialization.ISerializable Interface来模拟这个Sample),它将以By Value的方式在不同的Application Domain之间传递。

MarshalByRefType:继承自System. MarshalByRefObject。该类型的对象它将以By Value的方式在不同的Application Domain之间传递。"
--------------------------------------------
我来纠正一个笔误哈: MarshalByRefType 这里"By Value"按下文的意思应该写"By Reference" ^_^
  回复  引用  查看    

#5楼[楼主] 2007-03-06 09:25 Artech      

@Jeffrey Zhao
谢谢,我也经常光顾你的Blog,非常不错。我很喜欢你写的关于Ajax的东西。
  回复  引用  查看    

#6楼[楼主] 2007-03-06 09:28 Artech      

@Anders.Zhao
谢谢你Anders.Zhao!
粗心的毛病老是改不了。

  回复  引用  查看    

#7楼 2007-03-06 09:39 BlueMountain[未注册用户]

不鸣则已 一鸣惊人   回复  引用    

#8楼 2007-03-06 09:46 JesseZhao      

呵呵,文章进步很快哦   回复  引用  查看    

#9楼 2007-03-06 09:50 dudu      

好文章!谢谢分享!
已收录至精华区。
  回复  引用  查看    

#10楼[楼主] 2007-03-06 09:51 Artech      

@JesseZhao
一回生,二回熟嘛。呵呵。
  回复  引用  查看    

#11楼 2007-03-06 10:04 yukaizhao[未注册用户]

写的真好,羡慕你的钻研精神,向你学习,不仅知其然,而且要知其所以然.

  回复  引用    

#12楼[楼主] 2007-03-06 10:22 Artech      

@yukaizhao
这样一来可满足好奇心,而来可以长见识,何乐不为呢?呵呵!
  回复  引用  查看    

#13楼 2007-03-06 10:34 GoGoSonny      

看不懂55555怎么办?   回复  引用  查看    

#14楼 2007-03-06 10:55 BlueMountain[未注册用户]

好像是不能运行阿


try
{
GeneralType generType = (GeneralType)appDomain.CreateInstanceAndUnwrap( assemblyName, "AppDemo.GeneralType" );

}
catch( Exception ex )
{
Console.WriteLine( "Fail to create the GeneralType instrance" );
Console.WriteLine( ex.Message );
Console.WriteLine( ex.StackTrace );
}



我得到的是

Fail to create the GeneralType instrance
Type 'AppDemo.GeneralType' in assembly 'AppDemo, Version=1.0.0.0, Culture=neutra
l, PublicKeyToken=null' is not marked as serializable.
at System.AppDomain.CreateInstanceAndUnwrap(String assemblyName, String typeN
ame)
at AppDemo.Program.Main(String[] args) in d:\Documents and Settings\rudai.che
n\My Documents\Visual Studio 2005\AnsyncDemo\ConsoleClient\AppDemo\AppDemo\Progr
am.cs:line 44



难道 GeneralType需要被序列化才能被CreateInstanceAndUnwrap么? 查了一下csdn,没有介绍
  回复  引用    

#15楼 2007-03-06 11:00 BlueMountain[未注册用户]




ex.GetType() == System.Runtime.Serialization.SerializationException


可是CSDN上面介绍的CreateInstanceAndUnwrap这个函数不会抛出这种异常阿

BTW 我使用的是VS2005 D版
  回复  引用    

#16楼[楼主] 2007-03-06 12:08 Artech      

@BlueMountain
Exception不是发生在CreateInstanceAndUnwrap上。实际上这个方法成功在新的Application Domain中创建了一个Instance。当这个Instance传递到Current AppDomain是,由于它不能序列化,所以异常抛出。
  回复  引用  查看    

#17楼[楼主] 2007-03-06 12:29 Artech      

@BlueMountain
我重新运行了程序,没有未被Handle的Exception呀。
  回复  引用  查看    

#18楼 2007-03-06 16:41 BlueMountain[未注册用户]

to Artech : 非常感谢你的提示,“Exception不是发生在CreateInstanceAndUnwrap上。实际上这个方法成功在新的Application Domain中创建了一个Instance。当这个Instance传递到Current AppDomain是,由于它不能序列化,所以异常抛出。” 但是如果我紧紧使用下面这段代码,这个exception还是会throw,这样子是不是说虽然我“成功在新的Application Domain中创建了一个Instance”,

try
{
appDomain.CreateInstanceAndUnwrap(assemblyName, "Artech.AppDomainIsolation.GeneralType");
}
catch (Exception ex)
{
Console.WriteLine("Fail to pass a general type instance created in another application domain to default appliction domain,beacuse \"{0}\"", ex.Message);
}

上面这段代码看起来并没有“这个Instance传递到Current AppDomain”啊?

是不是说我写的上面这段代码本身就是把instance传递到了Current AppDomain了阿?



非常感谢大虾您对我这个小菜鸟的帮助,如果您愿意,您可是mail给我,或者是加我的MSN: BlueMountain_1980@hotmail.com
  回复  引用    

#19楼 2007-03-06 17:22 耶耶[未注册用户]

写得真不错

NET交流群(上海站):36840133
  回复  引用    

#20楼 2007-03-06 17:49 eeee[未注册用户]

lz的内容在CLR Via C# 第二版(English) 第525-537有详细描述,大家可以进行更深入的阅读。   回复  引用    

#21楼[楼主] 2007-03-06 18:19 Artech      

@eeee
不好意思,昨天在回复你的时候,不小心点错了,结果把你的回复删掉了。
我也强烈建议大家读一读Jeff Richiter的CLR Via C#(2nd Edition)。除了English Edition,中文也早已出现先市面上了,比英文版便宜很多。
  回复  引用  查看    

#22楼[楼主] 2007-03-06 21:22 Artech      

@BlueMountain
对你的严谨的态度表示钦佩.
实际上当你调用appDomain.CreateInstanceAndUnwrap(assemblyName, "Artech.AppDomainIsolation.GeneralType"); 由于它会返回一个GeneralType 的Instance,虽然你没有把它赋给某一个变量,但并不意味着这得Instance就不回产生。你可以认为它是赋给了一个存在于Current AppDomain临时变量。
如果你这样调用:
AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, "Artech.AppDomainIsolation.GeneralType");
则不会有Exception。
所以你说的CreateInstanceAndUnwrap是不会抛出System.Runtime.Serialization.SerializationException 是成立的。
  回复  引用  查看    

#23楼[楼主] 2007-03-08 13:19 Artech      

这是我在另一个讨论AppDomian的随笔中的Comment:
........我不同意这种说法。Application Domain不存在什么Parent和Child之分(我想你是想说Default AppDomain 和新创建的AppDomain)。AppDomain之间是平级的关系。
AppDomain按照的加载Assembly的方式可以分为Neutral AppDomian和non-neutral domain(我不知道是否有这样的官方命名,我自己想的名字)。Assembly如果被加载到Neutral AppDomian,这个Assembly已经被JIT的code是可以被其他AppDomain共享的(这个共享范围由加载Assembly选择的Option来决定——SingleDomain,MultipleDomain,MultiDomainHost)。比如Mscorlib这Assembly就被加载到neutral domain中为所有的AppDomain所共享。
但绝大多数AppDomain是neutral domain,加载在这些AppDomain中的Assemblly被该AppDomain私人占有。各个AppDomain是相互隔离的,在一个AppDomain中创建的对象不能被另一个AppDomain直接调用,AppDomain这种隔离性反应的物理内存上就是每个AppDomain独享一块内存空间。
至于什么时候会加载Assembly,只要它所要用到的Type没有在该AppDomain和可用的neutral domain中加载的Assembly中定义,它就会加载对应的Assembly。一旦Assembly在加载,除非你卸载AppDomain,否则你是无论如何不可能把它卸载的。
  回复  引用  查看    

#24楼 2007-05-22 10:45 Anders06      

厉害厉害,长见识了   回复  引用  查看    

#25楼[楼主] 2007-05-22 11:22 Artech      

@Anders06
发现园子的Anders还真不少,莫非又是Anders Hejlsberg 的拥趸:)
  回复  引用  查看    

#26楼 2007-08-28 10:46 邱晨[未注册用户]

欢迎来我们.NET技术交流群:12339353
本群是.NET开发人员的交流空间,欢迎广大.NET开发者来我们群一起讨论,研究.NET技术。(凑热闹的、其他语言开发人员以及喜欢涉及多门语言的误扰!)
  回复  引用    

#27楼 2007-09-03 15:22 simone[未注册用户]

2005,偶等好久了   回复  引用    

#28楼[楼主] 2007-09-03 23:11 Artech      

@simone
What do you mean?
  回复  引用  查看    

#29楼 2008-04-07 12:40 Kratos[未注册用户]

@Artech
domain-neutral vs per-domain:
if an assembly is loaded domain-neutral, we just mean that all these data structures and code are available in all the different AppDomains.
If that same assembly is loaded per-domain, we have to duplicate all those structures between AppDomains.
  回复  引用    

#30楼[楼主] 2008-04-07 12:54 Artech      

@Kratos
http://www.cnblogs.com/artech/archive/2007/05/06/737130.html
http://www.cnblogs.com/artech/archive/2007/06/04/769805.html
  回复  引用  查看    

#31楼 2008-04-25 17:02 KratosX[未注册用户]

// The first parameter should be named assemblyFile, but it was incorrectly named in a previous
// release, and the compatibility police won't let us change the name now.
public Object AppDomain.CreateInstanceFromAndUnwrap(String assemblyName,
String typeName)
{
ObjectHandle oh = CreateInstanceFrom(assemblyName, typeName);
if (oh == null)
return null;

return oh.Unwrap();
} // CreateInstanceAndUnwrap
There is the code of AppDomain.CreateInstanceFromAndUnwrap method. we can see that this mehtod just call the AppDomain.CreateInstanceFrom and then Unwrap the ObjectHandle.
Now, you can try to call AppDomain.CreateInstanceFrom insteaded without any exception.
  回复  引用    

#32楼 2008-07-16 15:10 eone[未注册用户]

using Artech.AppDomainIsolation;

这个怎么得到?
  回复  引用    

#33楼[楼主] 2008-07-16 18:13 Artech      

@eone
????
  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 664825




相关文章:

相关链接: