.NET基礎:應用程序域AppDomain
- 2019 年 11 月 5 日
- 筆記
最近再搞.NET中的插件開發,其中涉及到應用程序的熱升級,在很多情況下、我們希望用戶對應用程序的升級是無感知的,並且儘可能不打斷用戶操作的。
雖然在Web 或者 WebAPI上,由於多點的存在可以逐個停用單點進行系統升級,而不影響整個服務。但是 客戶端卻不能這樣做,畢竟用戶一直在使用着。
那麼有沒有一種方式,可以在用戶無感知的情況下(即、不停止進程的情況下)對客戶端進行升級呢?
答案是肯定的, 這就是我今天想說的、可以對應用程序進行熱升級。當然這種方式也同樣適用於 ASP.NET ,這裡最核心的就是需要理解:應用程序域AppDomain
不過當前隨筆是以 WPF為例子的,並且原理是一樣的、代碼邏輯也是一樣的。
一、應用程序域AppDomain
在介紹插件技術之前、我們需要先了解一些基礎性的知識,第一個就是應用程序域AppDomain.
操作系統和運行時環境通常會在應用程序間提供某種形式的隔離。 例如,Windows 使用進程來隔離應用程序。 為確保在一個應用程序中運行的代碼不會對其他不相關的應用程序產生不良影響,這種隔離是必需的。這種隔離可以為應用程序域提供安全性、可靠性, 並且為卸載程序集提供了可能。
在 .NET中應用程序域AppDomain是CLR的運行單元,它可以加載應用程序集Assembly、創建對象以及執行程序。
在 CLR 里、AppDomain就是用來實現代碼隔離的,每一個AppDomain可以單獨創建、運行、卸載。
如果默認AppDomain監聽了 UnhandledException 事件,任何線程的任何未處理異常都會引發該事件,無論線程是從哪個AppDomain中開始的。
如果一個線程開始於一個已經監聽了 UnhandledException事件的 app domain, 那麼該事件將在這個app domain 中引發。
如果這個app domian 不是默認的app domain, 並且 默認 app domain 中也監聽了 UnhandledException 事件, 那麼 該事件將會在兩個app domain 中引發。
CLR啟用時,會創建一個默認的AppDomain,程序的入口點(Main方法)就是在這個默認的AppDomain中執行。
AppDomain是可以在運行時進行動態的創建和卸載的,正因如此,才為插件技術提供了基礎(註:應用程序集和類型是不能卸載的,只能卸載整個AppDomain)。
AppDomain和其他概念之間的關係
1、AppDomain vs 進程Process
AppDomain被創建在Process中,一個Process內可以有多個AppDomain。一個AppDomain只能屬於一個Process。
2、AppDomain vs 線程Thread
應該說兩者之間沒有關係,AppDomain出現的目的是隔離,隔離對象,而 Thread 是 Process中的一個實體、是程序執行流中的最小單元,保存有當前指令指針 和 寄存器集合,為線程(上下文)切換提供可能。如果說有關係的話,可以牽強的認為一個Thread可以使用多個AppDomain中的對象,一個AppDomain中可以使用多個Thread.
3、AppDomain vs 應用程序集Assembly
Assembly是.Net程序的基本部署單元,它可以為CLR提供元數據等。
Assembly不能單獨執行,它必須被加載到AppDomain中,然後由AppDomain創建程序集中的類型 及 對象。
一個Assembly可以被多個AppDomain加載,一個AppDomain可以加載多個Assembly。
每個AppDomain引用到某個類型的時候需要把相應的assembly在各自的AppDomain中初始化。因此,每個AppDomain會單獨保持一個類的靜態變量。
4、AppDomain vs 對象object
任何對象只能屬於一個AppDomain,AppDomain用來隔離對象。 同一應用程序域中的對象直接通信、不同應用程序域中的對象的通信方式有兩種:一種是跨應用程序域邊界傳輸對象副本(通過序列化對對象進行隱式值封送完成),一種是使用代理交換消息。
二、創建 和 卸載AppDomain
前文已經說明了,我們可以在運行時動態的創建和卸載AppDomain, 有這樣的理論基礎在、我們就可以熱升級應用程序了 。
那就讓我們來看一下如何創建和卸載AppDomain吧
創建:
AppDomainSetup objSetup = new AppDomainSetup(); objSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; this.domain = AppDomain.CreateDomain("RemoteAppDomain", null, objSetup);
創建AppDomain的邏輯非常簡單:使用 AppDomain.CreateDomain 靜態方法、傳遞了一個任意字符串 和 AppDomainSetup 對象。
卸載:
AppDomain.Unload(this.domain);
卸載就更簡單了一行代碼搞定:AppDomain.Unload 靜態方法,參數就一個 之前創建的AppDomain對象。
三、在新AppDomain中創建對象
上文已經說了創建AppDomain了,但是創建的新AppDomain卻是不包含任何對象的,只是一個空殼子。那麼如何在新的AppDomain中創建對象呢?
this.remoteIPlugin = this.domain.CreateInstance("PluginDemo.NewDomain", "PluginDemo.NewDomain.Plugin").Unwrap() as IPlugin;
使用剛創建的AppDomain對象的實例化方法: this.domain.CreateInstance,傳遞了兩個字符串,分別為 assemblyName 和 typeName.
並且該方法的重載方法 和 相似功能的重載方法多達十幾個。
四、影像複製程序集
創建、卸載AppDomain都有、創建新對象也可以了,但是如果想完成熱升級,還有一點小麻煩,那就是一個程序集被加載後會被鎖定,這時候是無法對其進行修改的。
所以就需要打開 影像複製程序集 功能,這樣在卸載AppDomain後,把需要升級的應用程序集進行升級替換,然後再創建新的AppDomain即可了。
打開 影像複製程序集 功能,需要在創建新的AppDomain時做兩步簡單的設定即可:
AppDomainSetup objSetup = new AppDomainSetup(); objSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; // 打開 影像複製程序集 功能 objSetup.ShadowCopyFiles = "true"; // 雖然此方法已經被標記為過時方法, msdn備註也提倡不使用該方法, // 但是 以.net 4.0 + win10環境測試,還必須調用該方法 否則,即便卸載了應用程序域 dll 還是未被解除鎖定 AppDomain.CurrentDomain.SetShadowCopyFiles(); this.domain = AppDomain.CreateDomain("RemoteAppDomain", null, objSetup);