• 《CLR via C#》笔记——AppDomain(1)


    一,Appdomain概述

      CLR COM服务器初始化时,会创建一个AppDomain。AppDomain是一组程序集的逻辑容器。宿主可以通过CLR创建额外的AppDomain。AppDomain的唯一作用就是隔离。下面是它的具体功能。

    ●一个AppDomain中的代码创建的对象不能由另一个AppDomain中的代码直接访问。

    ●AppDomain可以卸载。

    ●AppDomain可以单独保护。AppDomain在创建后,会应用一个权限集,它决定了在这个AppDomain中运行的程序集的最大权限。

    ●AppDomain可以单独实施配置。AppDomain在创建后,会关联一组配置设置。这些设置主要影响CLR在AppDomain中加载程序集的方式。这些设置涉及搜索路径,版本绑定重定向,卷影复制及加载器优化。

    二,AppDomain的进程模型

      一个Windows进程中的AppDomain数量没有硬性限制。每个AppDomain都有一个Loader堆,每个Loader堆记录了AppDomain自创建以来访问过的类型,每个类型都有一个方法表,方法表的每个记录项都指向Jit编译的本地代码(前提是该方法至少执行过一次)。我们来看看,在Windows进程中加载AppDomain后的模型图。

      如图所示,AppDomain #1中加载了MyApp.exe,TypeLib.dll,System.dll三个程序集;AppDomain #2中加载了WindABC.dll,System.dll两个程序集;System.dll程序集被加载到了两个AppDomain中。如果这两个AppDomain都使用了来自System.dll的一个类型,那么在两个AppDomain的Loader堆中,都会为同一类型分配一个类型对象;类型对象的内存不会为两个AppDomain共享。另外,一个AppDomain中的代码调用一个类型定义的方法时,方法的IL代码会进行JIT编译,生成本地(native)代码将与每个AppDomain相关联;方法的代码不由调用它的所有AppDomain共享。

      不共享类型对象的内存和本地代码,这当然是一种浪费。但是,AppDomain的全部目的就是提供隔离性;CLR要求在卸载某个AppDomain并释放它的所有资源的同时,不会对其他的AppDomain产生负面影响。通过复制CLR的数据结构而不共用,就可以保证这一点。除此之外,还能保证多个AppDomain使用的一个类型在AppDomain中都有一组静态字段。

      有的程序集本来就要由多个AppDomain使用,最典型的例子就是MSCorLib.dll。该程序集包含了System.Object,System.Int32以及其他所有与.Net Framework密不可分的类型。CLR初始化时,该程序集会自动加载,而且所有的AppDomain都共享该程序集的类型。为了减少资源的消耗,MSCorLib.dll程序集以一种“AppDomain中立”的方式加载。也就是说,针对以“AppDomain中立”方式加载的程序集,CLR会为它们维护一个特殊的Loader堆。该Loader堆中所有的类型对象,以及为这些类型定义的方法JIT编译生成的所有本地代码,都会被进程中的所有AppDomain共享。遗憾的是,共享这些资源带来的收益并不是没有代价的。这个代价就是,以“AppDomain中立”的方式加载的所有程序集永远不能卸载。为了回收他们的资源,唯一的办法就是终止Windows进程,让Windows去回收资源。

    三,跨越AppDomain边界访问对象

      一个AppDomain中的代码可以和另一个AppDomain中的类型和对象通信。但是,只允许通过良好定义的机制访问这些类型和对象。下面的代码演示了构造以下三种类型时不同的行为:一个“按引用封送”(Marshal-by-Reference),一个“按值封送”(Marshal-by-Value),一个完全不能封送的类型。

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Reflection;
    using System.Runtime.Remoting;
    
    namespace AppDomainLib
    {
        public class Marshal
        {
            private static void Marshaling()
            {
                //获取AppDomain的一个引用(“调用线程”在该AppDomain中执行)
                AppDomain adCallingThreadDomain = Thread.GetDomain();
    
                //每个AppDomain都有一个友好字符串名称,获取这个名称并显示
                string callingDomainName = adCallingThreadDomain.FriendlyName;
                Console.WriteLine("Defalut AppDomain's friendly name={0}", callingDomainName);
    
                //获取&显示我们的AppDomain中包含“Main”方法的程序集
                string exeAssembly = Assembly.GetEntryAssembly().FullName;
                Console.WriteLine("Main assembly={0}", exeAssembly);
    
                //定义一个局部变量引用一个AppDomain
                AppDomain ad2 = null;
    
                //*** Demo 1,使用Marshal-by-Reference进行跨AppDomain通信 ***
                Console.WriteLine("{0}*** Demo #1", Environment.NewLine);
    
                //新建一个AppDomain,安全性和配置匹配与当前的AppDomain
                ad2 = AppDomain.CreateDomain("AD #2", null, null);
                MarshalByRefType mbrt = null;
    
                //将我们的程序集加载到AppDomain中,构造一个对象,把它封送会我们的AppDomain
                //实际上得到的是一个代理引用
                mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "AppDomainLib.MarshalByRefType");
    
                Console.WriteLine("Type={0}", mbrt.GetType());//这里CLR在类型上撒谎了,得到Type=AppDomainLib.MashalByRefType,其实并不是这样
    
                //证明得到的是一个代理的引用
                Console.WriteLine("Is Proxy={0}", RemotingServices.IsTransparentProxy(mbrt));
    
                //看起来像是在MashalByRefType上调用了一个方法,实在不然
                //我们是在代理类型上调用了一个方法,代理使线程切换至拥有对象
                //的那个AppDomain
                mbrt.SomeMehtod();
    
                //卸载新的AppDomain
                AppDomain.Unload(ad2);
    
                //mbrt引用了一个无效的代理对象,代理对象引用了一个无效的AppDomain
                try
                {
                    mbrt.SomeMehtod();
                }
                catch (AppDomainUnloadedException)
                {
                    Console.WriteLine("Fall Call");
                }
    
                //*** Demo 2,使用Marshal-by-Value进行跨AppDomain通信 ***
                Console.WriteLine("{0}*** Demo #2", Environment.NewLine);
    
                //新建一个AppDomain,安全性和配置匹配与当前的AppDomain
                ad2 = AppDomain.CreateDomain("AD #2", null, null);
    
                //将我们的程序集加载到AppDomain中,构造一个对象,把它封送会我们的AppDomain
                //实际上得到的是一个代理引用
                mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "AppDomainLib.MarshalByRefType");
    
                //对象的方法返回所返回对象的一个副本
                //返回的对象是按值(而非引用)封送
                MarshalByValType mbv = mbrt.MethodWidthReturn();
    
                //证明我们得到的不是对一个代理对象的引用
                Console.WriteLine("Is Porxy={0}", RemotingServices.IsTransparentProxy(mbv));
    
                //看起来像是在MarshalByValType上调用方法,事实确实如此
                Console.WriteLine("Return Object create:{0}", mbv.ToString());
    
                //卸载AppDomain
                AppDomain.Unload(ad2);
    
                //mbv引用有效的对象,卸载AppDomain没有影响
                try
                {
                    //我们是在对象上调用一个方法,所有不会抛出异常
                    Console.WriteLine("Return Object create:{0}", mbv.ToString());
                }
                catch (AppDomainUnloadedException)
                {
                    Console.WriteLine("Fail Call");
                }
    
                //*** Demo 3 使用不可封送的类型进行AppDomain通信 ****
                Console.WriteLine("{0}*** Demo #3", Environment.NewLine);
    
                //新建一个AppDomain,安全性和配置匹配与当前的AppDomain
                ad2 = AppDomain.CreateDomain("AD #2", null, null);
    
                //将我们的程序集加载到AppDomain中,构造一个对象,把它封送会我们的AppDomain
                //实际上得到的是一个代理引用
                mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "AppDomainLib.MarshalByRefType");
    
                //对象的方法返回一个不可封送的对象,抛出异常
                NonMarshalableType nmt= mbrt.MethodArgAndReturn(callingDomainName);
    
                //这里的代码永远执行不到。。。
            }
    
            public static void Main()
            {
                Marshaling();
            }
        }
    
        // 该类的实例可跨越AppDomain的边界“按引用封送”
        public sealed class MarshalByRefType : MarshalByRefObject
        {
            public MarshalByRefType()
            {
                Console.WriteLine("{0} .ctor running in {1}", this.GetType().Name, Thread.GetDomain().FriendlyName);
            }
    
            public void SomeMehtod()
            {
                Console.WriteLine("Executing is " + Thread.GetDomain().FriendlyName);
            }
    
            public MarshalByValType MethodWidthReturn()
            {
                Console.WriteLine("Executing is " + Thread.GetDomain().FriendlyName);
                MarshalByValType t = new MarshalByValType();
                return t;
            }
    
            public NonMarshalableType MethodArgAndReturn(string callingDomainName)
            {
    // 注意callingDomainName是可以序列化的
                Console.WriteLine("Calling from {0} to {1} ", callingDomainName, Thread.GetDomain().FriendlyName);
                NonMarshalableType t = new NonMarshalableType();
                return t;
            }
        }
    
        // 该类的实例可跨越AppDomain的边界“按值封送”
        [Serializable]
        public sealed class MarshalByValType : Object
        {
            private DateTime m_CreateTime = DateTime.Now;//注意DateTime是可序列化的
    
            public MarshalByValType()
            {
                Console.WriteLine("{0} ctor running in {1},create on {2}", this.GetType().ToString(),
                    Thread.GetDomain().FriendlyName, m_CreateTime);
            }
    
            public override string ToString()
            {
                return m_CreateTime.ToLongDateString();
            }
        }
    
        // 该类的实例不可跨越AppDomain进行封送
        //[Serializable]
        public sealed class NonMarshalableType : Object
        {
    
            public NonMarshalableType()
            {
                Console.WriteLine("Executing in {0}", Thread.GetDomain().FriendlyName);
            }
        }
    }

    程序的运行结果如下:

    View Code
    *** Demo #1
    MarshalByRefType .ctor running in AD #2
    Type=AppDomainLib.MarshalByRefType
    Is Proxy=True
    Executing is AD #2
    Fall Call
    
    *** Demo #2
    MarshalByRefType .ctor running in AD #2
    Executing is AD #2
    AppDomainLib.MarshalByValType ctor running in AD #2,create on 2012/07/06 16:24:07
    Is Porxy=False
    Return Object create:2012年月日
    Return Object create:2012年月日
    
    *** Demo #3
    MarshalByRefType .ctor running in AD #2
    Calling from AppDomainLib.vshost.exe to AD #2 
    Executing in AD #2
    'System.Runtime.Serialization.SerializationException' 例外发生。。。

      CLR不允许一个AppDomain中的变量引用另一个AppDomain中创建的对象。如果CreateInstanceAndUnwrap函数只返回对象的引用,AppDomain提供的隔离性就会被打破,而隔离是AppDomain的全部目的!因此在CreateInstanceAndUnwrap返回对象之前,它要执行一些额外的逻辑。

      CreateInstanceAndUnwrap导致调用线程从当前AppDomain转至新的AppDomain,它们用的是同一个线程,所有从这一点也可以看出,线程是可以跨越AppDomain的。并且,跨AppDoman边界的方法调用是同步执行的。如果希望多个AppDomain中的代码并非执行,应创建额外的线程。

    3.1 “按引用封送”

      当CreateInstanceAndUnwrap发现它封送的对象类型派生自MarshalByRefObject,CLR就会跨AppDomain边界按引用封送对象。下面讲述了按引用将一个对象从一个AppDomain(源AppDomain,这里是真正创建对象的地方)封送到另一个AppDomain(目标AppDomain,这里是调用CreateInstanceAndUnwrap的地方)的具体含义。

      源AppDomain想向目标AppDomain发送或返回一个对象的引用时,CLR会在目标AppDomain的Loader堆中定义一个代理类型。这个代理类型是用原始类型的元数据生成的。因此,他和原始数据看起来完全一样。有一样的实例成员(事件,属性,方法)。但是实例成员不会成为代理类型的一部分。在这个代理类型中,确实定义了自己的几个实例字段,但这些实例字段和原始数据不一致。相反,这些字段只是用于指出那个AppDomain“拥有”真实的对象,以及如何在拥有对象的AppDomain中找到真实的对象。(在内部,代理对象用一个GCHandle实例引用真实的对象)

      这个代理类型在目标AppDomain中定义好之后,CreateInstanceAndUnwrap方法就会创建这个代理类型的实例,初始化它的字段来标识源AppDomain和真实对象,然后将对这个代理对象的引用返回目标AppDomain。CLR一般不允许将一个类型的对象转换成一个不兼容的类型。但在当前这种情况下,CLR允许转型,因为新类型和源类型有相同的实例成员。事实上,用代理对象调用GetType方法,他会向你撒谎,说自己是一个MarshalByRefObject对象。System.Runtime.Remoting.RemotingServices.IsTransparentProxy方法可以用来验证这个对象是一个代理对象。

      AppDomain的Unload静态方法会强制CLR卸载指定的AppDomain(包括其中加载的程序集),并强制执行一次垃圾回收,以释放由卸载AppDomain中的代码创建的对象。这时,默认的AppDomain中mbrt变量仍然引用了一个有效的代理对象。但代理对象已不再引用一个有效的AppDomain了(它已经被卸载了)。当试图再次使用代理对象调用SomeMethod方法时,代理的SomeMethod方法会抛出一个AppDomainUnloadedException异常。

      由于新创建的AppDomain是没有根的,所以代理引用的原始对象可以被垃圾回收器回收。这当然不理想。但另一方面,如果将原始对象不确定的留在内存中,代理可能不再引用它,而原始对象依然存活,这同样不理想。CLR解决这个问题的办法是使用一个“租约管理器”。一个对象的代理创建好之后,CLR保持对象存活5分钟,如果5分钟之内没有通过代理发出调用,对象就会失效,下次垃圾回收会释放它的对象。每发出一次对对象的调用,“租约管理器”都会续订对象的租期,保证它在接下来的2分钟在内存中保持存活。如果在对象过期之后试图通过一个代理调用它,CLR会抛出一个System.Runtime.Remoting.RemotingException。默认的5分钟和2分钟是可以修改的,你只需要重写MarshalByRefObject的InitializeLifetimeService方法。更多的详情,可以参看SDK文档的“生存期租约”主题。

    3.2“按值封送”

      按值封送的类型,需要实现Serializable特性。源AppDomain想向目标AppDomain发送或返回一个对象的引用时,CLR将对象的实例字段序列化成一个字节数组。这个字节数组从源AppDomain复制到目标AppDomain。然后在目标AppDomain中反序列化字节数组,这会强制CLR将定义了“被反序列化的类型”的程序集加载到目标AppDomain中(如果还未加载的话)。接着,CLR创建类型的一个实例,并用字节数组中的值初始化对象的字段,使之与原对象的值相同。换言之,CLR在目标AppDomain中复制了源对象。然后CreateInstanceAndUnwrap返回对这个副本的引用;这样一来,对象就跨AppDomain的边界按值封送了。按值封送不会涉及代理,返回的对象被默认的AppDomain“拥有”。

    3.3 使用不可封送的类型跨越AppDomain

      注意这个例子,调用方法是传给了源AppDomain一个String类型的callingDomainName参数,因为String类有Serializable特性,所以能正常传入。函数返回时,返回了一个没有Serializable特性的类型,所以抛出了异常。

    未完,下接 《CLR via C#》笔记——AppDomain(2)

  • 相关阅读:
    eclipse实现热部署和热启动
    java.sql.SQLException: Incorrect string value: 'xF0x9Fx98x87<b
    Springboot 热部署中存在冲突的问题
    在执行save操作时候出现的诡异!
    富文本编辑器选择使用
    Springboot 1.5.x版本上读取自定义配置文件问题
    Springboot中静态资源和拦截器处理(踩了坑)
    表单验证——jquery validate使用说明【另一个教程】
    表单验证——jquery validate使用说明
    注册页面
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/2594623.html
Copyright © 2020-2023  润新知