• 寄宿 和 应用程序域(二)


    本篇主要讲述AppDomain的使用。

    基础部分请看上一篇 寄宿 和 应用程序域(一)

    先看一个使用AppDomain的实例1:

    寄宿代码如下:

     1 namespace LibraryOne
     2 {
     3     [Serializable]
     4     public class Class1
     5     {
     6         public void DoSomething(int max)
     7         {
     8             Console.WriteLine("当前AppDomain:{0}",System.Threading.Thread.GetDomain().FriendlyName);
     9             for (int i = 0; i < max; i++)
    10             {
    11                 Console.WriteLine(i);
    12             }
    13         }
    14     }
    15 }

    调用代码如下: 

     1 namespace MyProject
     2 {
     3     class Program
     4     {
     5         static void Main()
     6         {
     7             //获取当前默认AppDomain
     8             AppDomain domain1 = Thread.GetDomain();
     9 
    10             Console.WriteLine("默认AppDomain名称:" + domain1.FriendlyName);
    11 
    12             //创建新的AppDomain           
    13             //此处传递null,使domain2的权限及配置信息与当前AppDomain相同。
    14             AppDomain domain2 = AppDomain.CreateDomain("domainname2", null, null);
    15 
    16             Console.WriteLine("domain2名称:" + domain2.FriendlyName);
    17 
    18             //加载独立程序集
    19             var assembly = Assembly.LoadFrom("LibraryOne.dll");
    20            
    21             var s = (LibraryOne.Class1)domain2.CreateInstanceAndUnwrap(assembly.FullName, "LibraryOne.Class1");
    22 
    23             s.DoSomething(3);
    24 
    25             //卸载AppDomain
    26             AppDomain.Unload(domain2);
    27 
    28             Console.ReadKey();
    29         }
    30     }
    31 }

    结果:

    可以发现,DoSomething()的执行,发生在默认AppDomain,即domain1。为什么?(后面解答)

     以上是一个AppDomain使用的简单实例,其中包含了AppDomain从创建,调用,到卸载的过程。

    AppDomain 创建
      AppDomain提供了静态方法CreateDomain(),创建AppDomain
      AppDomain.CreateDomain提供了5种重载方式,可以用于定义所创建AppDomain的 友好名称,初始化信息,权限信息,受信任程序集等信息。


    AppDomain 卸载
      AppDomain 提供了静态方法AppDomain.Unload(),用于卸载指定的 AppDomain 。
      卸载过程大致如下:
      1、CLR挂起进程中所有执行过寄宿代码的线程。
      2、检查上述所有线程的线程栈,对于正在执行 或即将执行 被卸载AppDomain的线程,CLR会强迫其抛出异常ThreadAbortExecption。这将导致异常处理块finally中的代码执行,以清理资源。
        如果没有异常捕捉,CLR使相关线程终止,进程继续运行。
      3、当上一步所有线程都离开 被卸载AppDomain后,CLR遍历堆,为所有与 被卸载AppDomain 有关的代理设置一个标识,是这些代理知道其所引用的真实对象已经不存在。
        此时,任何代码对这些代理的方法调用,都会抛出AppDomainUnloadException.
      4、CLR强制垃圾回收,对已卸载AppDomain创建的任何对象进行内存回收。
      5、CLR恢复剩余所有线程的执行。

    对于AppDomian的调用执行,在下面实例中可以看到。

    AppDomain 间通信:
      AppDomain提供的主要功能就是隔离。但有时我们有需要在AppDomain间通信。
      CRL提供了一些机制用于AppDomain间的通信,当然这种通信并不破坏 AppDomain的隔离性。

    一、按引用封送的 跨域传值
      假设AppDomain1 要与AppDomain2中的寄宿代码通信。
      按引用封送的的做法是,在AppDomain2中创建寄宿代码的类型实例,当然此时不能返回该实例,返回了该实例,AppDomain的隔离就无从谈起了。
      而是返回给AppDomain1一个代理,这个代理中包含了一些信息。通过这些信息,我们可以知道创建实例的AppDomain,以及如何在这个AppDomain中找到这个实例。
      于是在AppDomain1中,我们通过这个代理来对AppDomain2中实例进行调用。执行时,线程会根据代理中的信息返回AppDomain2中执行具体的代码。
      通过这种方式,实现了AppDomain间的通信,同时又保持了隔离的特点。
      若要实现按引用封送的 跨域传值,寄宿代码必须继承类型:MarshalByRefObject

    演示实例2

    寄宿代码:

     1 namespace LibraryOne
     2 {
     3     //若要实现按引用封送的 跨域传值,寄宿代码必须继承类型:MarshalByRefObject
     4     public class Class2:MarshalByRefObject
     5     {
     6         public void DoSomething2(int max)
     7         {
     8             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
     9             for (int i = 0; i < max; i++)
    10             {
    11                 Console.WriteLine(i);
    12             }
    13         }
    14     }
    15 }

    调用代码:

     1 namespace MyProject
     2 {
     3     class Program
     4     {
     5         static void Main()
     6         {            
     7             //获取当前默认AppDomain
     8             AppDomain domain1 = Thread.GetDomain();
     9 
    10             Console.WriteLine("默认AppDomain名称:" + domain1.FriendlyName);
    11 
    12             //创建新的AppDomain            
    13             AppDomain domain2 = AppDomain.CreateDomain("domainname2", null, null);
    14 
    15             Console.WriteLine("domain2名称:" + domain2.FriendlyName);
    16 
    17             //加载独立程序集
    18             var assembly = Assembly.LoadFrom("LibraryOne.dll");
    19 
    20             //返回一个ObjectHandle,ObjectHandle也继承了MarshalByRefObject。
    21             //其值是返回的代理。代理中有成员保存了该代理实例创建自domain2,程序集为LibraryOne,类型为Class2,以及在domain2中如何找到Class2实例等信息。
    22             //此创建过程在domain2中执行,代理返回到domain1
    23             var s = domain2.CreateInstance(assembly.FullName, "LibraryOne.Class2");
    24             
    25             //返回ObjectHandle包装的对象。类型为object。值仍为代理。
    26             var m = s.Unwrap();
    27 
    28             //类型转换,转换为Class2,值仍为代理。
    29             var n = (LibraryOne.Class2)m;
    30 
    31             //验证是否为代理
    32             Console.WriteLine("Is proxy:{0}", RemotingServices.IsTransparentProxy(n));
    33 
    34             //调用Class2中的方法。
    35             //此代码执行时,会根据代理中信息回到domain2中找到Class2实例并执行。
    36             n.DoSomething2(2);
    37 
    38             //上述代码执行完毕,仍回到当前AppDomain,即domain1。
    39             Console.WriteLine("当前AppDomain名称:" + Thread.GetDomain().FriendlyName);
    40 
    41             //卸载AppDomain
    42             AppDomain.Unload(domain2);
    43 
    44             Console.ReadKey();
    45         }
    46     }
    47 }

     结果:

    n是一个代理,DoSomething2()的执行发生在domain2中。

     

    二、按值封送的 跨域传值

      按值封送的的跨域通信,的处理方式是。结合下面实例3来看。

      domain1为当前AppDomain,domain2为寄宿代码运行的AppDomain。

      这里按值封送的的跨域通信的做法是,先按照按引用封送的的方式获取一个Class4实例的代理A。

      Class4实例提供的方法RetrunClass3()可以返回一个 可序列化的Class3实例。

      此时通过代理A,调用RetrunClass3方法,线程会返回domain2中创建一个Class3对象B。

      此时,domain2向domain1返回B的引用时。CLR会将B序列化,并将序列化后的数据返回给domain1,domain1通过反序列化在domain1中得到一个B对象的复制品C。

      通过这种方式,我们可以把domain2中的实例,拿到domain1中使用。此时,源实例B仍存在于domian2中。

    演示实例3:

    寄宿代码:

     1 namespace LibraryOne
     2 {
     3     [Serializable]
     4     public class Class3
     5     {
     6         public void DoSomething3(int max)
     7         {
     8             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
     9             for (int i = 0; i < max; i++)
    10             {
    11                 Console.WriteLine(i);
    12             }
    13         }
    14     }
    15     public class Class4 : MarshalByRefObject
    16     {
    17         public Class3 class3 = null;
    18         public Class3 RetrunClass3()
    19         {
    20             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
    21             if (class3 == null) class3 = new Class3();
    22             return class3;
    23         }
    24         public void DoSomething4(int max)
    25         {
    26             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
    27             for (int i = 0; i < max; i++)
    28             {
    29                 Console.WriteLine(i);
    30             }
    31         }
    32         public void CallClass3DoSomething(int max)
    33         {
    34             if (class3 != null) class3.DoSomething3(max);
    35         }
    36     }
    37 
    38 }

    调用代码:

     1 namespace MyProject
     2 {
     3     class Program
     4     {
     5         static void Main()
     6         {
     7             //获取当前默认AppDomain
     8             AppDomain domain1 = Thread.GetDomain();
     9 
    10             Console.WriteLine("默认AppDomain名称:" + domain1.FriendlyName);
    11 
    12             //创建新的AppDomain            
    13             AppDomain domain2 = AppDomain.CreateDomain("domainname2", null, null);
    14 
    15             Console.WriteLine("domain2名称:" + domain2.FriendlyName);
    16 
    17             //加载独立程序集
    18             var assembly = Assembly.LoadFrom("LibraryOne.dll");
    19 
    20             //返回一个Object
    21             //其值是返回的代理。此创建过程在domain2中执行,代理返回到domain1
    22             var s = domain2.CreateInstanceAndUnwrap(assembly.FullName, "LibraryOne.Class4");
    23 
    24             //类型转换,转换为Class4,值仍为代理。
    25             var n = (LibraryOne.Class4)s;
    26 
    27             //验证是否为代理
    28             Console.WriteLine("Is proxy:{0}", RemotingServices.IsTransparentProxy(n));
    29 
    30             //调用Class4中的方法。此代码执行时,会根据代理中信息回到domain2中找到Class4实例并执行。
    31             n.DoSomething4(4);
    32 
    33             var c3 = n.RetrunClass3();
    34             //验证是否为代理
    35             Console.WriteLine("Is proxy:{0}", RemotingServices.IsTransparentProxy(c3));
    36             c3.DoSomething3(3);//通过调用结果可以知道,此执行过程发生在domain1
    37 
    38             n.class3.DoSomething3(2);
    39 
    40             n.CallClass3DoSomething(5);
    41 
    42             //上述代码执行完毕,仍回到当前AppDomain,即domain1。
    43             Console.WriteLine("当前AppDomain名称:" + Thread.GetDomain().FriendlyName);
    44 
    45             //卸载AppDomain
    46             AppDomain.Unload(domain2);
    47 
    48             Console.ReadKey();
    49         }
    50     }
    51 }

    调用36行 c3.DoSomething3(3)后

    结果:

    通过输出可以看到,这个过程是在domain1中执行的。为什么?

    c3是domain2中源实例在domain1中的复制品,并且是一个完整的对象,它无法穿过AppDomain去调用domain2中的源实例,只能在domain1中执行。

     

    那么有没有方法可以执行到domain2中的源对象呢?Class4中有个源对象的实例。我们调用试试。 

    代码38行  n.class3.DoSomething3(2)

    结果:

    通过调用结果,看到任然是在domain1中执行的。为什么?

    我们知道,此时n是代理,通过这个代理获取class3时,由于class3是可序列化的。返回给我们的仍然是class3的复制品,这中间仍然存在序列化反序列化的过程发生。

     

      实际上 这个例子并不是按值封送 最直接的例子。本章的实例1  才是按值封送 最简单直接的例子。

      实例1中,由于Class1是可以序列化的,通过代理对其进行调用时。会返回给宿主AppDomain一个反序列化的Class1实例,所以在实例1中,通过输出结果可以看到,执行时发生在domain1中的。

      在这个例子之所以稍复杂,是为了在讲述按值封送的同时 演示两种封送方式的配合使用。

    按值封送的关键是被封送的类型必须可序列化。只要通过代理去获取可序列化的对象,都会在宿主AppDomain中得到一个反序列化后的实例。

     

    那么,我有方法可以调用到domain2中的源对象吗?

    有的,但是要通过代理中的方法去调用,而不能直接通过代理去获取对象。

    代码40行  n.CallClass3DoSomething(5);

    结果:

    看到这个输出结果,可以发现,执行时在domain2中发生的。

     

    实际上按引用封送的,可以方便我们将domain1中的代码传入domain2中执行。

    按值封送的 可以让我们将domain2中代码拿到domain1中执行。

    将这两种方式结合起来,我们就可以让代码根据我们的需求穿梭于domain1和domain2之间。同时,当前线程也穿梭于两个AppDomain之间工作。

     

    虽然演示实例中,都是domain2寄宿于domain1。实际上,我们也可以让domain1的代码寄宿于domain2执行。寄宿可以是互相的。

     

  • 相关阅读:
    前端进击的巨人(一):执行上下文与执行栈,变量对象
    读书笔记(06)
    前端博客收藏
    Nodejs-Can't set headers after they are sent
    Mac OS安装包管理工具Homebrew教程
    webpack自动化构建脚本指令npm run dev/build
    使用express搭建node中间件
    【转】基于localStorage的资源离线和更新技术
    web前端性能优化
    Vue生命周期详解
  • 原文地址:https://www.cnblogs.com/qingzhuo/p/3945082.html
Copyright © 2020-2023  润新知