最近在對AppDomain編程時遇到了一個問題,卸載AppDomain後,在內存中還保留它加載的DLL的數據,因此即便卸載掉AppDomain,仍是沒法更新它加載的DLL.看來只有關閉整個進程來更新DLL了.web
--------------------------------------------------------------------------------------------------------------編程
咱們知道,進程是操做系統用於隔離衆多正在運行的應用程序的機制。在.Net以前,每個應用程序被加載到單獨的進程中,併爲該進程指定私有的虛擬內存。進程不能直接訪問物理內存,操做系統經過其它的處理把這些虛擬內存映射到物理內存或IO設備的某個區域,而這些物理內存之間不會有重疊,這就決定了一個進程不可能訪問分配給另外一個進程的內存。相應地,運行在該進程中的應用程序也不可能寫入另外一個應用程序的內存,這確保了任何執行出錯的代碼不會損害其地址空間之外的應用程序。在這種機制下,進程做爲應用程序之間一個獨立而安全的邊界在很大程度上提升了運行安全。
進程的缺點是下降了性能。許多一塊兒工做的進程須要相互通訊,而進程卻不能共享任何內存,你不能經過任何有意義的方式使用從一個進程傳遞到另外一個進程的內存指針。此外,你不能在兩個進程間進行直接調用。你必須代之以使用代理,它提供必定程度的間接性。雖然,使用動態鏈接庫dll讓全部的組件運行在同一空間,必定程度上能夠提升性能,但這些組件相互影響,一個組件的錯誤將極有可能致使整個應用程序的崩潰,「dll地獄」更是讓許多應用程序難以免。
瀏覽器
在.Net中,應用程序有了一個新的邊界:應用程序域(如下簡稱域)。它是一個用於隔離應用程序的虛擬邊界。爲了禁止不該交互的代碼進行交互,這種隔離是必要的。.Net的應用程序在域層次上進行隔離,一個域中的應用程序不能直接訪問另外一個域中的代碼和數據。這種隔離使得在一個應用程序範圍內建立的全部對象都在一個域內建立,確保在同一進程中一個域內運行的代碼不會影響其餘域內的應用程序,大大提升了運行的安全。
.Net結構中,因爲公共語言運行庫可以驗證代碼是否爲類型安全的代碼,因此它能夠提供與進程邊界同樣大的隔離級別,其性能開銷也要低得多。你能夠在單個進程中運行幾個域,而不會形成進程間調用或切換等方面的額外開銷。這種方法是把任何一個進程分解到多個域中,容許多個應用程序在同一進程中運行,每一個域大體對應一個應用程序,運行的每一個線程都在一個特殊的域中。若是不一樣的可執行文件都運行在同一個進程空間中,它們就能輕鬆地共享數據或直接訪問彼此的數據。這種代碼同運行同一個進程但域不一樣的類型安全代碼一塊兒運行時是安全的。在一個進程內運行多個應用程序的能力顯著加強了服務器的可伸縮性。
安全
域是.Net 帶來的一個重要改進,它不只將衆多在運行的應用程序隔離開來,還不影響彼此間通訊。雖然,公共語言運行庫禁止在不一樣域中的對象之間進行直接調用,但咱們能夠複製這些對象,或經過代理訪問這些對象。若是之前一種方式,那麼對該對象的調用爲本地調用。也就是說,調用方和被引用的對象位於同一域中。若是經過代理訪問對象,調用方和被引用的對象位於不一樣的域中,對該對象的調用被視爲遠程調用,這種情形與兩個進程間的調用或兩臺計算機間的調用結構大體相同。這時,須要被引用對象的元數據對於兩個域都可用,以便.Net即時編譯JIT能正確執行。
服務器
在.Net中,線程是公共語言運行庫用來執行代碼的操做系統構造。在運行時,全部託管代碼均加載到一個域中,由特定的操做系統線程來運行。然而,域和線程之間並不具備一一對應關係。在任意給定時間,單個域中能夠執行不止一個線程,並且特定線程也並不侷限在單個域內。也就是說,線程能夠跨越域邊界,不爲每一個域建立新線程。固然,在指定時刻,每一線程都只能在一個域中執行。運行庫會跟蹤全部域中有哪些線程正在運行。經過調用.Net類庫的 Thread.GetDomain 方法,你還能夠肯定正在執行的線程所在的域。
app
做爲公共語言運行庫的隔離單元,域在進程中建立和運行。.Net結構中,運行時宿主(也叫做運行時主機)是負責將運行時載入進程並在域中執行用戶代碼和託管代碼的應用程序。運行時宿主包括ASP.Net、瀏覽器Internet Explorer 和 Windows等外殼程序,負責建立進程和默認域,例如,Asp.Net爲每一個運行在web服務器上的web應用程序建立一個域。瀏覽器Internet explore建立運行受管制控件的域。
對多數應用程序,你並沒必要須建立相應的域,每次CLR在初始化一個進程時,將建立默認域,並使該進程運行於這個默認域下。然而,默認域不能由任何系統調用來卸載,該域只有在進程被卸載以後才能被銷燬。若是直接在默認域下編程或運行代碼,而因爲某種緣由域的代碼崩潰了,那麼就有使得整個服務隨之崩潰的風險。
因而,針對不一樣的應用程序,應該建立和配置相應的域並載入適當的程序集。.Net爲此提供了豐富的類庫。其中,AppDomain 類是域的編程接口,其大量的(重載)方法能完成如下任務:
· 建立域
· 在域中加載程序集和類型
· 枚舉域中的程序集和線程
· 卸載域
建立新域時,使用AppDomain 類的靜態方法CreateDomain。你能夠爲域命名並按該名稱來引用域。下面的示例語句建立新域,併爲它指定名稱 MyDomain:
框架
<ccid_code>AppDomain myDomain = AppDomain.CreateDomain("MyDomain"); |
<ccid_nobr>
而後你能夠查詢當前域的名稱和新建立子域的名稱:
dom
<ccid_code>string hostDomain=AppDomain.CurrentDomain.FriendlyName; string childDomain=myDomain.FriendlyName; |
<ccid_nobr>
在這裏,屬性FriendlyName表示的是域的友好名稱,友好名稱經過從程序集的基本代碼中去除目錄路徑而造成。例如,文件名爲 "d:\MyAppDomain\MyAssembly.exe" 的程序集加載到默認域中,域的友好名稱就是 "MyAssembly.exe"。
更通常的是,在建立域以前,先設置好域的參數,這能夠經過類AppDomainSetup來完成。該類的ApplicationBase 屬性定義應用程序的根目錄, AppDomainSetup 類還有一個極重要的屬性變量LoaderOptimizzation,取值能夠是MultiDomain,MultiDomainHost和SignleDomain等,用以指定被加載程序集的類別(共享程序集或域專用程序集),例如,如下語句把程序集設置爲域專用程序集:
性能
<ccid_code>appDomainSetup.LoaderOptimization=LoaderOptimizatiion.SigleDomain; |
<ccid_nobr>
對以上兩個方面簡單概括一下,對域的典型操做就包括:設置參數而後建立兩個步驟,語句示例以下:
spa
<ccid_code>AppDomainSetup appDomainSetup=new AppDomainSetup();//實例化域設置 appDomainSetup.LoaderOptimization=LoaderOptimization.SingleDomain; //指定域類別 AppDoman ad=AppDomain.CreateDomain(domainName,appDomainSetup); //建立域 ... //應用程序在這裏運行代碼 ... AppDomain.Unload(ad);//卸載域 |
<ccid_nobr>
當使用完域時,可以使用AppDomain類Unload()靜態方法將其卸載。要卸載進程中在運行的託管代碼,只能卸載代碼運行時所在的域而不能卸載單獨的程序集或類型,Unload方法會正常關閉指定的域。這時,載入域的全部程序集都會被移除,而且沒法再使用。不過,若是域中的程序集對域是非特定的(域無關程序集,也即共享程序集),則程序集的數據會保留在內存中,直到整個進程關閉。除了關閉整個進程,沒有機制能夠卸載非特定於域的程序集。在某些狀況下,卸載應用程序域的請求不起做用,並致使 CannotUnloadAppDomainException。因爲一個進程中容許包含多個域,某個域能夠在不中止整個進程的狀況下卸載。以這樣的方式卸載再也不須要的代碼,能夠減小內存佔用並極大提升應用程序的可縮放性。此外,因爲線程並不與域一一對應,當域中存在活動線程時,調用AppDomain.Unload方法可能沒法將域卸載並致使異常。
另外,ICorRuntimeHost 接口包括一個名爲 Stop 的方法,宿主可使用該方法從進程中強制卸載運行庫。當調用 Stop 時,將當即卸載全部域(包括默認域和全部非特定於域的代碼),並從進程中所有移除運行庫。當對進程調用 Stop 後,不能將運行庫加載回該進程。要再次開始運行託管代碼,必須建立一個新的進程。
從上面的論述不難看出:要運行應用程序,必須首先將程序集(.Net下經編譯產生,包含IL中間語言、元數據及清單等)加載到域中。並且一個域中可裝載多個程序集。默認狀況下,公共語言運行庫自動將一個程序集加載到包含引用該程序集的代碼的域。經過此方法,該程序集的代碼和數據獨立於使用該程序集的應用程序。
自行建立域的好處之一即是能夠指定如何裝載程序集。在域中有如下兩種方式加載程序集:
一、將當前程序集加載入單獨的域中,同一個程序集可能有多個副本;
二、以非特定於域的形式加載程序集,讓一個程序集在多個域間共享;
這兩種方式各自偏重於安全性和性能,須要視具體狀況在兩者之間權衡。具體地,在 .Net 框架中,System.Reflection.Assembly 類提供如下靜態方法將程序集加載至域:
· Load()在給頂程序集名稱的前提下,加載該程序集:
<ccid_code>Assembly SampleAssembly; … SampleAssembly = Assembly.Load("System.Data");//根據類型加載程序集 |
<ccid_nobr>
· LoadFrom()在已知程序集文件名或路徑等信息的狀況下加載程序集:
<ccid_nobr>
<ccid_code>Assembly SampleAssembly; … SampleAssembly = Assembly.LoadFrom("c:\\Sample.Assembly.dll");//根據已有程序集名稱加載 |