.NET AppDomain

.NET AppDomain

操作系统启动托管程序后,会调用 CLR 来托管该程序,CLR 在初始化时会默认创建一个 AppDomain 来运行托管代码。AppDomain 是为了隔离而设计的,它将程序集限定在某个域中执行,而不影响其它域的状态,它可以极大地提高托管程序的稳定性:

  • 支持动态卸载程序集:在插件架构中,不必等到进程结束时才释放,避免不必要的内存占用
  • 实现了程序集隔离:将一些容易引起崩溃的代码单独运行在一个 AppDomain 中
  • 限制代码安全权限:对不同的 AppDomain 应用不同的安全级别
  • 加载相同程序集的不同版本:在同一个进程中使用多个 AppDomain 加载不同版本的两个程序集

AppDomain 特征

AppDomain 可理解为 .NET 进程,与线程相比,它是一个静态的概念。

  • 一个 AppDomain 中的代码创建的对象不能由另一个 AppDomain 中的代码直接访问
  • AppDomain 可以单独卸载:卸载当前 AppDomain 中的所有程序集
  • AppDomain 可以单独保护:保证不会破坏宿主本身的重要数据结构
  • AppDomain 可以单独实施配置:加载方式(搜索路径、版本绑定重定向、加载器优化等)

上图展示了一个 Windows 进程,其中寄宿了 CLR 和两个 AppDomain 。每个 AppDomain 都会在各自的 Loader Heap 中独立地加载相关的程序集,都会独立地维护类型的静态字段。如果 CLR 要求卸载某个 AppDomain 并释放其所有资源时,不会影响到其它 AppDomain 的运行,这便是 AppDomain 的隔离效果。

对于一些基础类型的程序集,比如 MSCoreLib.dll(其中包含了 System.Object、System.Int32 以及其它 .NET Framework 密不可分的类型),CLR 采用了 “AppDomain 中立” 的方式加载,所有 AppDomain 都共享,以减少资源消耗。

跨越 AppDomain 边界访问对象

1. 按引用封送(Marshal-by-Reference)

public class MarshalByRefType : MarshalByRefObject
{
    public void SomeMethod()
    {
        Console.WriteLine($"Current AppDomain: {AppDomain.CurrentDomain.FriendlyName}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"Default AppDomain : {AppDomain.CurrentDomain.FriendlyName}");

        // 新建一个 AppDomain(安全性和配置匹配于当前 AppDomain)
        AppDomain newDomain = AppDomain.CreateDomain("New AppDomain", null, null);

        // 将程序级加载到新的 AppDomain 中,并封送回原 AppDomain 中(实际得到的是一个代理对象的引用)
        MarshalByRefType refType = (MarshalByRefType)newDomain.CreateInstanceAndUnwrap("AppDomainDemo", "AppDomainDemo.MarshalByRefType");

        // 看一下类型,CLR 在撒谎
        Console.WriteLine($"Type : {refType.GetType()}");

        // 证明是一个代理对象的引用
        Console.WriteLine($"IsTransparentProxy : {RemotingServices.IsTransparentProxy(refType)}");

        // 实际是在 New AppDomain 中调用的方法
        refType.SomeMethod();

        // 卸载 New AppDomain,代理对象将引用一个无效的 AppDomain
        AppDomain.Unload(newDomain);

        try
        {
            refType.SomeMethod();
            Console.WriteLine("Successful call...");
        }
        catch (AppDomainUnloadedException)
        {
            Console.WriteLine("Failed call...");
        }

        // Wait to exit...
        Console.ReadKey();
    }
}
Default AppDomain : AppDomainDemo.exe
Type : AppDomainDemo.MarshalByRefType
IsTransparentProxy : True
Current AppDomain: New AppDomain
Failed call...

代理对象的首次租期为 5分钟,如果 5 分钟内未被使用,将被回收。首次使用后,租期变为 2 分钟,即连续 2 分钟为被调用,将被回收。

2. 按值封送(Marshal-by-Reference)

[Serializable]
public class MarshalByValType
{
    public void SomeMethod()
    {
        Console.WriteLine($"Current AppDomain: {AppDomain.CurrentDomain.FriendlyName}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"Default AppDomain : {AppDomain.CurrentDomain.FriendlyName}");

        // 新建一个 AppDomain(安全性和配置匹配于当前 AppDomain)
        AppDomain domain = AppDomain.CreateDomain("New AppDomain", null, null);

        MarshalByValType person = (MarshalByValType)domain.CreateInstanceAndUnwrap("AppDomainDemo", "AppDomainDemo.MarshalByValType");
        Console.WriteLine($"IsTransparentProxy : {RemotingServices.IsTransparentProxy(person)}");

        person.SomeMethod();
        AppDomain.Unload(domain);

        try
        {
            person.SomeMethod();
            Console.WriteLine("Successful call...");
        }
        catch (AppDomainUnloadedException)
        {
            Console.WriteLine("Failed call...");
        }

        // Wait to exit...
        Console.ReadKey();
    }
}
Default AppDomain : AppDomainDemo.exe
IsTransparentProxy : False
Current AppDomain: AppDomainDemo.exe
Current AppDomain: AppDomainDemo.exe
Successful call...

如果某个对象既未继承自 MarshalByRefObject,也未标记 SerializableAttribute ,则在跨域访问时将抛出 SerializationException 异常。

AppDomain 卸载

Assembly 未提供卸载程序集的方法,只能通过 AppDomain 来卸载。

可使用 AppDomain.Unload() 静态方法来卸载某个 AppDomain ,从而卸载程序集。其提供 10 秒钟时间让线程从该 AppDomain 离开,具体步骤如下:

  1. 挂起进程中执行过托管代码的所有线程
  2. 检查所有线程,对与该 AppDomain 相关的线程抛出一个 ThreadAbortException
  3. 对所有引用卸载应用程序域里对象的代理对象设置一个标识(引发 AppDomainUnloadedException)
  4. 垃圾回收器会回收所有要卸载应用程序域里的对象
  5. CLR 恢复所有线程的执行

AppDomain 监视与异常通知

  • 监视
    • MonitoringSurvivedProcessMemorySize:当前 CLR 实例中所有 AppDomain 正在使用的字节数
    • MonitoringTotalAllocatedMemorySize:特定 AppDomain 已分配的字节数
    • MonitorigSurvivedMemorySize:特定 AppDomain 正在使用的字节数
    • MonitoringTotalProcessorTime:特定的 AppDomain 的 CPU 占用率
  • 异常通知
    • AppDomain.CurrentDomain.UnhandledException
    • AppDomain.CurrentDomain.FirstChanceException

AppDomain 应用

  • Silverlight 富 Internet 应用程序:每个 Silverlight 控件就是一个 AppDomain
  • ASP.NET Web 窗体应用程序:不关闭 Web 服务器,动态更改代码
  • MySQL Server:存储过程在自己的安全 AppDomain 中运行

参考资料

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ironyho

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值