浅谈.NET中AppDomain的理解

一、走进.NET AppDomain

天哪,.NET Framwork的CLR真是巧妙呢!随着越来越多的对.Net底层编程的了解,一些诸如架构,处理过程的复杂难懂的细节完全的让我叹服,所以呢,再次错过我们之前忽视的细节只美事不可能的了,有个与CLR肩并肩协同工作的一个核心组件,叫做AppDomain,作为.NET Framework的一部分,AppDomain是一个微软引入的非常酷的概念。

为了更好的理解.Net的AppDomain和AppDomian是如何影响我们创建并在其上工作的,还是从头说起比较好,那么我让我们从在应用程序中点击一个按钮开始,无论何时我们启动一个应用程序,我们实际上启动了一个Win32的进程,并且在这个进程中运行我们的程序。这些进程使用那些诸如内存,对象,内核还有等等的一些资源,任何一个Win32进程包含至少一个线程(越来越多,后来就是多线程了),并且如果我们运行其他的任务或者从我们的应用程序中打开其他的应用程序,那么这些任务都会属于运行多线程集合的我们的那个Win32进程。

Win32进程有一个特点,那就是他和虚拟边界很相似,在进程内通信很容易,但是想要在某种水平上与Win32进程外通信就要收到诸多限制,为了与其他的Win32进程通信,我们需要一些特别的工作机制,因为有一些安全上下文需要考虑(security contexts),并且还需要考虑在特定系统中,Win32进程能做什么和不能做什么。

那么谁负责运行一个进程?一个进程成功的运行都需要涉及到哪些因素呢?进程的执行以及进程中我们的代码的运行都是在域(domain)和操作系统的权限下的,在维护一个活动状态的进程时,操作系统不得不处理许多负责的情况和要点,让我们来看一下实际情形吧。

考虑一个如下的情形,我们的一个服务器内寄宿着很多个web应用程序,或者说,我们可能不得不在我们系统中运行一堆Windows应用程序,现在,瞧瞧我们正在处理什么,瞧瞧我们得给这些应用程序什么吧,那就是资源!资源!更多得资源。我们的资源(内存)时非常安规的,并且在我们为不同客户同时运行程序的时候,安全问题开始出现了,所以迫切需要我们彻底禁止任何方式的在这些应用程序进程间通信(然而,这总是和需求有冲突),这样做会导致经常性的崩溃,经常一个进程用光了被另一个进程申请来的内存,然后就导致崩溃。不管怎样,我们的进程很糟糕,没啥新鲜的,这些频繁的运行时错误通常是由低效率的内存使用所引发的内存泄露,对象空引用,内存越界等等造成的。所以,大家越来越意识到,在一个多用户的环境下创建、运行、维护进程是非常非常昂贵的。因此,运行一大堆进程并不是个好主意,因为他们递增适应性不怎么好。

在这种情况下,一种非常精巧的解决方案应运而生,在同一个宿主进程下运行多个应用程序可以让我们使用更少的资源,这甚至会导致更快的执行过速度,但是有另一个每天都在发生的场景:一旦一个应用程序崩溃,所有其他的在这个相同进程中的应用程序就会像一副纸牌一样全部玩儿完!是的,我们说的就是这样的多米诺骨牌效应。

那现在怎么办呢?

使用.Net AppDomain吧。这个概念非常巧妙,有新意。配得上诺贝尔奖,引入Application Domain的主要目的,就是将我们的应用程序与其他的应用程序隔离开,Application Domains运行在一个单独的Win32进程里。与我们刚刚谈到的解决方案相同,通过Application Domain内运行我们的应用程序,来限制由内存泄漏引起的错误和崩溃。

因此,我们在一个应用程序域内运行应用程序,并且我们在单个Win32进程中同时运行多个应用程序域,借着CLR的运行托管代码的能力,我们可以进一步的减少泄露和崩溃(还要感谢CLR的垃圾收集器),在同一个应用程序域内对象之间直接通信,而存活在不同的应用程序域内中的对象,如果想要彼此通信的话,就要通过相互拷贝对象或者通过用于消息互换的代理了(通过引用)。

这就是Application Domain的关键之处,但是还有更多,Application Domain相当于在单Win32进程内运行的一个精巧的轻量级的进程,实际上,如同我们上面所说,我们可以在一个Win32进程中运行多个Application Domain,另一个AppDomain的优势就是,我们可以通过宿主(比如说Asp.Net)来干掉AppDomain,而不会影响到其他的正存在于那个进程中的AppDomain,所以,我们在Application Domain中的工作是独立的,更进一步地,我们可以通过销毁AppDomain来卸载掉加载那个AppDomain中地对象,神奇的.Net运行时通过接管对内存的控制来强制的使用AppDomain分隔机制,所以Win32进程中的AppDomain里使用的所有内存都由.Net运行时管理,这样我们就避免了刚开头时提及的,所有的诸如一个应用程序访问另一个应用程序的内存,之类的问题。也就避免了由崩溃引发的运行时错误问题,因此,我们实际上应用了一个安全层,隔离了当前的应用程序,与其他的应用程序分开,实际上讲,我们在创建类似与运行着的web services一样的应用程序时,Application Domain在其中扮演着一个安全方面和基础方面的关键角色。

二、AppDomain理解

为了保证代码的健壮性CLR希望不同服务功能的代码之间相互隔离,这种隔离可以创建多个进程来实现,但操作系统中创建进程是即耗时又耗资源的一件事,所以在CLR中引入了AppDomain的概念。AppDomain主要是用来实现同一个进程中的各AppDomain之间的隔离,AppDomain可以用以下特征来描述他的全貌:

1、AppDomain概念并不存在于操作系统中,而只存在于.Net中,并且AppDomain不可脱离进程单独存在,他是属于某一个CLR或寄宿着CLR的进程中的。

2、一个进程中可以有多个AppDomain,并且每个之间相互隔离(只保证安全代码的隔离,不安全代码并不能保证),因此可以理解为AppDomain是.Net进程中的“进程”,在一个AppDomain中创建的对象只属于本AppDomain,多个AppDomain之间的对象不能够相互访问,除非遵循CLR的一些规则。

3、.Net程序启动时,在进程中创建一个默认的AppDomain,入口代码将运行于此AppDomain,默认应用程序域只有在进程终止时才会被销毁,如果主动调用Unload去卸载默认应用程序域,会抛出一个CannotUnloadAppDomainException。

4、每个AppDomain都单独的加载程序集,这意味着在A应用程序域中加载了的程序集,并不一定在B应用程序域中也被加载了。每个AppDomain有单独的Loader堆,相互不影响,每个Loader推都记录了自AppDomain建立以后所访问过的类型,Loader堆中的每个类型对象都有一个方法表,这些放发表指向已经被JIT编译过的本地代码(前提是这些方法是已经被至少执行过一次的)。因为AppDomain是相互隔离的,所以相同的一个类中的方法,在A应用程序域中被JIT编译过的,但不一定在B应用程序域中也是被JIT编译过的,方法是否被JIT编译过取决于这些方法是否在本地的AppDomain被调用过。

5、当AppDomain被卸载时,此AppDomain中的程序集会被卸载,因为每个AppDomain加载的程序集都是独立的,所以每个应用程序域被卸载并不会影响其他的AppDomain中加载的程序集,另外本AppDomain的Loader堆也会被回收,每个程序域中的Loader堆时独立的,所以也不会影响到其他程序域中的Loader堆,因为Loader堆也是独立的(静态字段时存在于类型对象上的),所以一个类型中静态字段在不同应用程序域中也是不同的存在,所以静态字段也是不被影响的,唯一受影响的是线程,因为线程可以跨越应用程序域访问不同的应用程序域上的代码,后面我们会介绍当卸载一个应用程序域时如何对线程做处理。

6、有一种程序集可以被多个AppDomain使用,这种程序集叫做“AppDomain中立”的程序集,比如MSCorLib.dll,该程序集包含了System.Object、System.Int32以及其他的与.Net Framework比不可分的类型,这个程序集在CLR初始化时会自动加载,JIT会为这些程序集创建一个特殊的Loader堆,并且程序集中的方法被编译成本地代码可被所有AppDomain共享,这种程序集不可被卸载,只有当进程结束时这种程序集才会被卸载。

好了,我们来看看如何创建一个基本的应用程序域吧。

实际上,.Net Framework提供了一个漂亮的基类,他存在于System命名空间下,通过他我们可以显示的创建一个AppDomain,在我们的应用程序中,继承System.MarshalByRefObject基类,我们可以创建可以在不同应用程序域间通信的对象。

看看这个吧。一个使用C#创建AppDomain的应用程序,我们使用的是Windows控制台程序

 

 1     class Program
 2     {
 3         static void Main()
 4         {
 5             // Get and display the friendly name of the default AppDomain.
 6             string callingDomainName = Thread.GetDomain().FriendlyName;
 7             Console.WriteLine(callingDomainName);
 8 
 9             // Get and display the full name of the EXE assembly.
10             string exeAssembly = Assembly.GetEntryAssembly().FullName;
11             Console.WriteLine(exeAssembly);
12 
13             // Construct and initialize settings for a second AppDomain.
14             AppDomainSetup ads = new AppDomainSetup();
15             ads.ApplicationBase =
16                 System.Environment.CurrentDirectory;
17             ads.DisallowBindingRedirects = false;
18             ads.DisallowCodeDownload = true;
19             ads.ConfigurationFile =
20                 AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
21 
22             // Create the second AppDomain.
23             AppDomain ad2 = AppDomain.CreateDomain("AD #2", null, ads);
24 
25             // Create an instance of MarshalbyRefType in the second AppDomain. 
26             // A proxy to the object is returned.
27             MarshalByRefType mbrt =
28                 (MarshalByRefType)ad2.CreateInstanceAndUnwrap(
29                     exeAssembly,
30                     typeof(MarshalByRefType).FullName
31                 );
32 
33             // Call a method on the object via the proxy, passing the 
34             // default AppDomain's friendly name in as a parameter.
35             mbrt.SomeMethod(callingDomainName);
36 
37             // Unload the second AppDomain. This deletes its object and 
38             // invalidates the proxy object.
39             AppDomain.Unload(ad2);
40             try
41             {
42                 // Call the method again. Note that this time it fails 
43                 // because the second AppDomain was unloaded.
44                 mbrt.SomeMethod(callingDomainName);
45                 Console.WriteLine("Sucessful call.");
46             }
47             catch (AppDomainUnloadedException)
48             {
49                 Console.WriteLine("Failed call; this is expected.");
50             }
51 
52             Console.ReadKey();
53         }
54     }
55 
56     public class MarshalByRefType : MarshalByRefObject
57     {
58         //  Call this method via a proxy.
59         public void SomeMethod(string callingDomainName)
60         {
61             // Get this AppDomain's settings and display some of them.
62             AppDomainSetup ads = AppDomain.CurrentDomain.SetupInformation;
63             Console.WriteLine("AppName={0}, AppBase={1}, ConfigFile={2}",
64                 ads.ApplicationName,
65                 ads.ApplicationBase,
66                 ads.ConfigurationFile
67             );
68 
69             // Display the name of the calling AppDomain and the name 
70             // of the second domain.
71             // NOTE: The application's thread has transitioned between 
72             // AppDomains.
73             Console.WriteLine("Calling from '{0}' to '{1}'.",
74                 callingDomainName,
75                 Thread.GetDomain().FriendlyName
76             );
77         }
78     }

ok,AppDomain就先介绍到这里,这真是一个巧妙的设计呢。

posted @ 2020-02-24 13:29  萌萌丶小魔王  阅读(2536)  评论(0编辑  收藏  举报