clr via c# clr寄宿和AppDomain

Posted frogkiller

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了clr via c# clr寄宿和AppDomain 相关的知识,希望对你有一定的参考价值。

1 clr寄宿-----.net framework在windows平台的顶部允许。者意味着.net framework必须用windows能理解的技术来构建。所有托管模块和程序集文件必须使用windows PE文件格式,而且要么是windows exe文件,要么是DLL文件

2,ICLRRuntimeHost可以做以下事情
①设置宿主管理器。该诉CLR宿主想参与与涉及以下操作的决策:内存分配、线程调度/同步以及程序集加载等。宿主还可声明它想获得有关垃圾回收启动和停止以及特定操作超时的通知
②获取CLR管理器。告诉CLR阻止使用某些类/成员。另外,宿主能分辨哪些代码可以调试,哪些不可以,以及当特定事件(例如AppDomain卸载、CLR停止或者堆栈移除异常)发生时宿主应调用哪个方法
③初始化并启动CLR
④加载程序集并执行其中的代码
⑤停止CLR,阻止任何更多的托管代码在Windows进程中运行

 



3,AppDomain

  • 一个AppDomain不能直接访问另外一个AppDomain的代码创建的对象
  • AppDomain可以卸载,卸载一个App Domain,可以卸载当前包含的所有程序集
  • App Domain可以单独保护.
  • App Domain可以单独配置
  • 一个进程,上面运行着一个CLR COM服务器.该CLR当前管理者两个AppDomain,每个App Domain都有着自己的Loader堆,上面记录了App Domain创建以来访问过的类型,每个类型对象都有一个方法表,方法表中的每个记录项都指向Jit编译过的本机代码.
  • 每个AppDomain都加载了程序集.

3,跨越AppDomain边界访问对象.

namespace ClrFromCSharp_2_2.LearnAppDomain
{
   public class AppDomainRef
    {
        public static void Marshalling()
        {
            AppDomain adCallingTreadDomain = Thread.GetDomain();//获取当前线程运行的域
            string callingDomainName = adCallingTreadDomain.FriendlyName;
            Console.WriteLine("Default AppDomain\'s friendly name={0}", callingDomainName);
            //获取并显示我们的AppDomain中包含了Main方法的程序集
            string exeAssembly = Assembly.GetEntryAssembly().FullName;
            Console.WriteLine("Main assembly={0}", exeAssembly);
            //定义局部变量来引用一个AppDomain
            AppDomain ad2 = null;
            Console.WriteLine("\\n Demo #1");
            ad2 = AppDomain.CreateDomain("AD #2", null, null);
            MarshalByRefType mbrt = null;
            mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType");//利用AppDomain和程序集创建对象.
            Console.WriteLine("Type={0}", mbrt.GetType());
            Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbrt));//
            //看起来我们像在MarshalByRefType上面调用一个方法,实则不然,我们在代理类型上调用一个方法,代理使线程切换到拥有对象的那个AppDomain,并在真实对象上调用
            //真实的方法.
            mbrt.SomeMethod();
            //卸载 AppDomain
            AppDomain.Unload(ad2);
            //mbrt 引用一个有效的代理对象,代理对象引用一个无效的AppDomain
            try
            {
                mbrt.SomeMethod();
                Console.WriteLine("Successful");

            }
            catch (AppDomainUnloadedException)
            {
                Console.WriteLine("Failed Call.");
            }
            //---Demo2---使用MarshalByValue进行跨AppDomain通信
            Console.WriteLine("\\n Demo #2");
            ad2 = AppDomain.CreateDomain("AD #2", null, null);
            mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType");//利用AppDomain和程序集创建对象.
            MarshalByValType mbvt = mbrt.MethodWithReturn();
            Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbvt));//证明得到的不是一个代理对象的引用
            Console.WriteLine("Returned object created" + mbvt.ToString());
            //卸载新的AppDomain
            AppDomain.Unload(ad2);
            //mbvt引用有效的对象:卸载AppDomain没用影响
            try
            {
                Console.WriteLine("Returned object created" + mbvt.ToString());
                Console.WriteLine("Successful");
            }
            catch (AppDomainUnloadedException)
            {
                Console.WriteLine("Failed Call.");
            }
            //Demo3 :使用不可封送的对象,抛出异常
            mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType");//利用AppDomain和程序集创建对象.
            NonMarshalableType nmt = mbrt.MethodArgAndReturn("abc");
            //这里永远执行不到...
        }
    }
    public sealed class MarshalByRefType : MarshalByRefObject
    {
        public MarshalByRefType()
        {
            Console.WriteLine("{0} ctor running in {1}", this, GetType().ToString(), Thread.GetDomain().FriendlyName);

        }
        public void SomeMethod()
        {
            Console.WriteLine("Executing in" + Thread.GetDomain().FriendlyName);
        }
        public MarshalByValType MethodWithReturn()
        {
            Console.WriteLine("Executing in" + Thread.GetDomain().FriendlyName);
            return new MarshalByValType();
        }
        public NonMarshalableType MethodArgAndReturn(string name)
        {
            Console.WriteLine("calling from {0} to {1}", name, Thread.GetDomain().FriendlyName);
            return new NonMarshalableType();
        }
    }
    [Serializable]
    public sealed class MarshalByValType : Object
    {
        private DateTime m_creationTime = DateTime.Now;

        public MarshalByValType()
        {
            Console.WriteLine("{0} ctor running in {1},Created On {2:D}", this.GetType().ToString(), Thread.GetDomain().FriendlyName, m_creationTime);
        }
        public override string ToString()
        {
            return m_creationTime.ToLongDateString();
        }
    }
    [Serializable]
    public sealed class NonMarshalableType : Object
    {
        public NonMarshalableType()
        {
            Console.WriteLine("Executing in" + Thread.GetDomain().FriendlyName);
        }
    }

}

运行结果

Default AppDomain\'s friendly name=ClrFromCSharp_2_2.exe
Main assembly=ClrFromCSharp_2_2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=11fccde6e91ad1e9

 Demo #1
ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType ctor running in ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType
Type=ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType
Is proxy=True
Executing inAD #2
Failed Call.

 Demo #2
ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType ctor running in ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType
Executing inAD #2
ClrFromCSharp_2_2.LearnAppDomain.MarshalByValType ctor running in AD #2,Created On 2020年2月13日
Is proxy=False
Returned object created2020年2月13日
Returned object created2020年2月13日
Successful

1,线程也就是方法中,可以通过Thread.Get Domain(),来获取域的信息.也可以通过System.AppDomain.CurrentDomain获取

2,FriendName:一个用于辨识的名称.

3,Assembly.GetEntryAssembly().FullName获取当前运行程序的程序集的强名称(Full Name)

4,利用AppDomain.CreateDomain()创建AppDomain

  • FriendName
  • System.Security.Policy.Evidence,传递null代表继承当前AppDomain的权限集证据.
  • System.AppDomainSetup.代表Domain的配置设置.传递Null代表使用当前AppDomain的配置

当新建了一个App Domain后,其Loader堆是空的.CLr不在这个App Domain上创建任何线程,AppDomain中也不会运行代码.除非显式调用代码.

5,利用mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "ClrFromCSharp_2_2.LearnAppDomain.MarshalByRefType");//利用AppDomain和程序集创建对象. ,注意,首先,利用该函数创建一个代理对象

两个参数.一个使程序集的FullName,一个是程序集所在的类的强名称.

mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(typeof(MarshalByRefType).Assembly.FullName, typeof(MarshalByRefType).FullName);//利用AppDomain和程序集创建对象.

使用这个函数时候,线程切换到AppDomain ad2,然后在上面创建类型对象(派生自MarshalByRefObject),然后,返回给实际的ad1一个引用的代理对象:

该代理对象有完全一样的实列成员(属性,事件,方法)但是,其不含实列的字段.相反,其含有

  • 字段1:指出在哪个AppDomain之中
  • 字段2:一个GCHandle,来引用真实的对象.

通过堆栈调式和Porxy代理方法可以知道,这是一个代理对象.当执行该代理对象的方法时,实际上线程切换到了ad2,并且在ad2上执行方法.

执行完毕后,其返回到原来的App Domain之中.

6,接下来,卸载了AppDomain.包括了所有程序集.并且强制执行一次垃圾回收.下一次调用实际上是调用代理上面的方法,代理发现方法的真实对象所在的APp已经卸载,抛出错误AppDomainUnloadedException异常.

7,对于派生自MarshalByRefObject派生的类的字段访问时,实际上调用System.Object的Field Getter或FieldSetter方法(利用反射)获取或者设定字段值.因此,访问字段性能很差.

8,

生存期租约.由于第二个AppDomain中的对象没有根,所以使用代理引用原始对象可以被垃圾回收.重写InitializeLifetimeService

public class MyClass : MarshalByRefObject
{
  [SecurityPermissionAttribute(SecurityAction.Demand,
                               Flags=SecurityPermissionFlag.Infrastructure)]
  public override Object InitializeLifetimeService()
  {
    ILease lease = (ILease)base.InitializeLifetimeService();
    if (lease.CurrentState == LeaseState.Initial)
    {
         lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
         lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
          lease.RenewOnCallTime = TimeSpan.FromSeconds(2);
    }
      return lease;
  }
}

9,按值封送对象([serialzible],但是未实现MarshalByRefObject:

      当一个对象被标识成[Serialable]时候,CLR将对象实列的字段序列化为一个字节数组,然后,CLR在目标的APPDOMAIN中反序列化对象,并且强制加载目标对象的程序集.也就是在目标AppDomain中精确复制了源对象.

10,当上述两种方法都不行时,则报错.


11,卸载AppDomain.

使用AppDomain.unload()来卸载AppDomain.

  1. 1.clr 挂起进程中执行过的托管代码的所有线程
  2. 2,LCR检查所有线程栈,查看哪些线程正在执行要卸载的App Domain中的代码,或那些线程会在某个时刻返回至要卸载的App Domain.
  3. CLR 会强迫对应的线程抛出一个ThreadAbortException(同时回复线程的执行).同时线程恢复执行.如果没有代码捕捉异常,CLR吞噬这个异常,线程会终止,但是进程会继续运行.这是很特别的一点,因为对于其他未经过处理的异常,CLR会终止进程.
  4. 3,当第二步发现所有线程都离开AppDomain后,CLR遍历堆,为所有引用了已卸载的domain创建的代理对象都设置一个标志.通知他们引用的真实对象已经不再了.现在任何代码在无效代理对象上调用方法都将抛出AppDomainUnloadedException异常
  5. 4,CLR强制垃圾回收,回收由已卸载的AppDomain创建的任何对象的内存.这些对象的Finalize方法被调用,使这些对象有机会清理他们占用资源
  6. 5,CLR恢复成语所有线程执行.Unload方法是同步的,如果10秒返回,则抛出一个CannotUnloadAppDomainException异常.

12,监视AppDomain

  • MonitoringSurvivedProcessMemorySize:当前进程所有domain分配的大小
  • MonitoringTotalAllocatedMemorySize:这个实列属性返回,实列Domain所有分配的内存大小()
  • MonitoringSurvivedMemorySize:这个实列属性返回,实列DOmain存活对象的内存大小()
  • MonitoringTotalProcessorTimer:这个实列返回实列Domain运行到当前的时间片段.
  •  public sealed class AppDomainMonitorDelta:IDisposable
        {
            private AppDomain m_appDomain;
            private TimeSpan m_thisADCpu;
            private Int64 m_thisADMemoryInUse;
            private Int64 m_thisADMemoryAllocated;
    
            static AppDomainMonitorDelta()
            {
                AppDomain.MonitoringIsEnabled = true;
            }
            public AppDomainMonitorDelta(AppDomain ad)
            {
                m_appDomain = ad ?? AppDomain.CurrentDomain;
                m_thisADCpu = m_appDomain.MonitoringTotalProcessorTime;
                m_thisADMemoryInUse = m_appDomain.MonitoringSurvivedMemorySize;
                m_thisADMemoryAllocated = m_appDomain.MonitoringTotalAllocatedMemorySize;
    
            }
            public void Dispose()
            {
                GC.Collect();
                Console.WriteLine("FriendlyName={0},cpu={1}ms", m_appDomain.FriendlyName, (m_appDomain.MonitoringTotalProcessorTime - m_thisADCpu).TotalMilliseconds);
                Console.WriteLine("Allocated {0:N0} bytes of which {1:N0} survived GCS", m_appDomain.MonitoringTotalAllocatedMemorySize - m_thisADMemoryAllocated,
                    m_appDomain.MonitoringSurvivedMemorySize - m_thisADMemoryInUse);
            }
        }
    

该类用于监视两个时间点之间AppDomain发生的变化

using(new AppDomainMonitorDelta(null))
            {
                var list = new List<object>();
                for (var x = 0; x < 2000; x++) { list.Add(new byte[10000]); }
                for (var x = 0; x < 2000; x++) { new Byte[10000].GetType(); }
                Int64 stop = Environment.TickCount + 5000;
                while (Environment.TickCount < stop) ;
            }

13,AppDomain FirstChanceException 异常通知---

  • 异常首次抛出的时候,CLR调用抛出异常的Domain的FirstChanceException回调方法.
  • 然后,CLR查找栈上任何catch块.如有处理,则正常运行,异常处理完成.如果没有,则沿着栈向上来到调用该Appdomain的Appdomain.
  • 通过序列化,反序列化,再次抛出该异常.在当前app domain中
  • 然后clr调用FirstChanceException方法.继续,第2步.
  • 如果都执行完毕还有异常没有处理,则终止进程.
  •  public class TestAppDomainFirstChanceNotify:MarshalByRefObject//创建一个引用传递的对象.
        {
            private string m_name;
            static TestAppDomainFirstChanceNotify()//在类初始化的时候,添加了当appdomain发生异常时,FirstChanceException添加事件.
            {
                Thread.GetDomain().FirstChanceException += (obj, e) =>
                  {
                      Console.WriteLine("FirstChanceCaused in {0}", Thread.GetDomain().FriendlyName);
                  };
            }
            public TestAppDomainFirstChanceNotify() : this("notify") { }
            public TestAppDomainFirstChanceNotify(string name)
            {
                m_name = name;
                Console.WriteLine("created at appdomain {0}", Thread.GetDomain().FriendlyName);//创建对象.
            }
            public void PullException(string message)//调用方法,抛出异常.
            {
                Console.WriteLine("try throw Exception at appdomain {0}", Thread.GetDomain().FriendlyName);
    
                throw new Exception(message);
    
    
            }
        }
    

结果:

public static void CallFirstChance()
        {
            Thread.GetDomain().FirstChanceException += FirstChangeHandler;//当当前appdomain发生第一次异常的时候,调用回调函数.
            AppDomain ad = AppDomain.CreateDomain("AD0");//创建新的app domain---ad;
            TestAppDomainFirstChanceNotify t = (TestAppDomainFirstChanceNotify)ad.CreateInstanceAndUnwrap(typeof(TestAppDomainFirstChanceNotify).Assembly.FullName, typeof(TestAppDomainFirstChanceNotify).FullName);
            try//创建代理对象
            {
                t.PullException("pull exception");//代理对象调用异常方法.
            }
            catch(Exception e)
            {
                Console.WriteLine("exception happened at {0}", Thread.GetDomain().FriendlyName);
            }



        }

created at appdomain AD0:创建对象
try throw Exception at appdomain AD0://抛出异常
FirstChanceCaused in AD0//调用ad0 中的FirstChanceException事件.
pull exception//调用当前app中的FirstChanceCaused
exception happened at ClrFromCSharp_2_2.exe//catch吃掉异常.

以上是关于clr via c# clr寄宿和AppDomain 的主要内容,如果未能解决你的问题,请参考以下文章

CLR via C# 阅读 笔记

CLR via C#

CLR via C# 笔记 -- 特性(18)

CLR via C#深解笔记六 - 泛型

《CLR via C#》之线程处理——协作式取消和超时

《CLR via C#》之线程处理——线程池与任务