最近再搞.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);