C# 關(guān)于AppDomain的一些總結(jié)
前言
一直想寫一個(gè)這樣的程序:與其它的程序完全解耦,但可以動(dòng)態(tài)的加載其它程序,并執(zhí)行其中的特定方法,執(zhí)行完后可以卸載,完全不影響該程序本身。最近無意間發(fā)現(xiàn)了 C# 中 AppDomain,再加上反射,感覺就是我所需要的。
基本概念
應(yīng)用程序域?yàn)榘踩?、可靠性、版本控制以及卸載程序集提供了隔離邊界。 應(yīng)用程序域通常由運(yùn)行時(shí)宿主創(chuàng)建,運(yùn)行時(shí)宿主負(fù)責(zé)在運(yùn)行應(yīng)用程序之前引導(dǎo)公共語言運(yùn)行時(shí)。
應(yīng)用程序域所提供的隔離具有以下優(yōu)點(diǎn):
(1)在一個(gè)應(yīng)用程序中出現(xiàn)的錯(cuò)誤不會(huì)影響其他應(yīng)用程序。 因?yàn)轭愋桶踩拇a不會(huì)導(dǎo)致內(nèi)存錯(cuò)誤,所以使用應(yīng)用程序域可以確保在一個(gè)域中運(yùn)行的代碼不會(huì)影響進(jìn)程中的其他應(yīng)用程序。
(2)能夠在不停止整個(gè)進(jìn)程的情況下停止單個(gè)應(yīng)用程序。 使用應(yīng)用程序域使您可以卸載在單個(gè)應(yīng)用程序中運(yùn)行的
注意:不能卸載單個(gè)程序集或類型。只能卸載整個(gè)域。
一切的根源,都是因?yàn)橹挥?Assembly.Load 方法,而沒有 Assembly.Unload 方法,只能卸載其所在的 AppDomain。
實(shí)踐
1. 首先準(zhǔn)備一個(gè)控制臺(tái)小程序
操作為讀取配置文件(為測(cè)試 AppDomain 中配置文件的讀取情況),并使用 Newtonsoft.Json 將其序列化為 json(為測(cè)試 AppDomain 中加載程序中的第三方引用情況),在控制臺(tái)輸出。項(xiàng)目名為 ReadPrint, 將其編譯為 exe 文件,并存放在 D:\AppDomainModules 中。
using Newtonsoft.Json; using System; using System.Configuration; namespace ReadPrint { class Program { static void Main(string[] args) { DoSomething(); } public static void DoSomething() { Person person = new Person { Account = ConfigurationManager.AppSettings["Account"], Name = ConfigurationManager.AppSettings["Name"], Age = int.Parse(ConfigurationManager.AppSettings["Age"]) }; Console.WriteLine(JsonConvert.SerializeObject(person)); Console.ReadLine(); } class Person { public string Account { get; set; } public string Name { get; set; } public int Age { get; set; } } } }
為了查看方便定義了 DoSomething 來執(zhí)行相關(guān)方法。也可以直接寫在 Main 方法中,調(diào)用時(shí)需要傳入?yún)?shù) args。因?yàn)樽罱K測(cè)試 AppDomain 的程序也打算使用控制臺(tái)應(yīng)用,也使用控制臺(tái)應(yīng)用來寫這個(gè)小程序。
2. 編寫使用 AppDomain 的程序
主要包含 AssemblyLoader.cs 文件用于封裝使用細(xì)節(jié),和 Program.cs 主程序文件。
AssemblyLoader.cs
using System; using System.IO; using System.Reflection; namespace AppDomainTest { public class AssemblyDynamicLoader { private AppDomain appDomain; public readonly RemoteLoader remoteLoader; public AssemblyDynamicLoader() { AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationName = "ApplicationLoader"; setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules"); setup.CachePath = setup.ApplicationBase; setup.ShadowCopyFiles = "true"; # 重點(diǎn) setup.ShadowCopyDirectories = setup.ApplicationBase; setup.ConfigurationFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules", "ReadPrint.exe.config"); //AppDomain.CurrentDomain.SetShadowCopyFiles(); this.appDomain = AppDomain.CreateDomain("ApplicationLoaderDomain", null, setup); String name = Assembly.GetExecutingAssembly().GetName().FullName; this.remoteLoader = (RemoteLoader)this.appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName); # 重點(diǎn) } public void Unload() { try { if (appDomain == null) return; AppDomain.Unload(this.appDomain); this.appDomain = null; } catch (CannotUnloadAppDomainException ex) { throw ex; } } } public class RemoteLoader : MarshalByRefObject { private Assembly _assembly; public void LoadAssembly(string assemblyFile) { try { _assembly = Assembly.LoadFrom(assemblyFile); } catch (Exception ex) { throw ex; } } public void ExecuteMothod(string typeName, string methodName) { if (_assembly == null) { return; } var type = _assembly.GetType(typeName); type.GetMethod(methodName).Invoke(Activator.CreateInstance(type), new object[] { }); } } }
其中類 RemoteLoader 為加載程序集的類,AssemblyDynamicLoader 類在此基礎(chǔ)上封裝了新建 AppDomain 的細(xì)節(jié)。
在 AssemblyDynamicLoader 的構(gòu)造函數(shù)中,為了測(cè)試方便,硬編碼了一些內(nèi)容,如 程序集文件查找路徑 PrivateBinPath 為當(dāng)前程序執(zhí)行目錄下面的 Modules 目錄,配置文件 ConfigurationFile 為 Modules 目錄中的 ReadPrint.exe.config, 以及創(chuàng)建新 AppDomain 時(shí)的程序集名稱。
AppDomainSetup 的屬性 ShadowCopyFiles(似乎可以譯為“卷影復(fù)制”) 代表是否鎖定讀取的程序集。如果設(shè)置為 true,則將程序集讀取至內(nèi)存,不鎖定其文件,這也是熱更新的前提;否則在程序執(zhí)行期間這些程序集文件會(huì)被鎖定,不能變化。
AppDomain 的方法 CreateInstanceAndUnwrap 意為在 AppDomain 的實(shí)例中創(chuàng)建指定類型的新實(shí)例,并返回。
在 RemoteLoader 的 ExecuteMethod 中,傳入的參數(shù)硬編碼為空。在實(shí)際使用時(shí)應(yīng)當(dāng)根據(jù)實(shí)際傳入?yún)?shù)。
Program.cs
using System; using System.IO; namespace AppDomainTest { class Program { static void Main(string[] args) { string modulesPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules"); DirectoryInfo di = new DirectoryInfo(modulesPath); if (!di.Exists) { di.Create(); } string remotePath = @"D:\AppDomainModules\"; string[] fileNames = new string[] { "ReadPrint.exe", "Newtonsoft.Json.dll", "ReadPrint.exe.config" }; foreach(var fileName in fileNames) { FileInfo fi = new FileInfo(Path.Combine(remotePath, fileName)); fi.CopyTo(Path.Combine(modulesPath, fileName), true); } AssemblyDynamicLoader adl = new AssemblyDynamicLoader(); adl.remoteLoader.LoadAssembly(Path.Combine(modulesPath, "ReadPrint.exe")); adl.remoteLoader.ExecuteMethod("ReadPrint.Program", "DoSomething"); adl.Unload(); } } }
在主程序文件中,創(chuàng)建 Modules 文件夾,拷貝程序文件、庫文件和配置文件。程序運(yùn)行結(jié)果:
可以看到成功調(diào)用了我們定義的 DoSomething 方法。
一些思考
1. 為什么不使用 AppDomain 實(shí)例的 Load 方法加載程序集
使用此方法,會(huì)首先在主程序的 AppDomain 中加載一遍程序集(和依賴),再移至我們創(chuàng)建的 AppDomain 中(特別注意,此時(shí)不會(huì)從我們新建的 AppDomain 的 PrivateBinPath 中搜索和加載)。
缺點(diǎn)有二,一是隨著程序的運(yùn)行,可能會(huì)加載大量的程序集,因此主程序的 AppDomain 也要加載大量程序集,而程序集無法單獨(dú)卸載,只有在主程序停止后才會(huì)卸載,其間必然越積越多,極不優(yōu)雅;二是無法自定目錄,主程序加載程序集和依賴時(shí)只會(huì)在其指定的 PrivateBinPath 中搜索,因此其它模塊所有需要的程序集文件都堆積在同一個(gè)目錄中,條理不清。
驗(yàn)證
修改 AssemblyDynamicLoader.cs 中的代碼,改為直接在構(gòu)造函數(shù)里面執(zhí)行程序加載,其它不變,并查看我們新建的 AppDomain 中已加載的程序集:
//String name = Assembly.GetExecutingAssembly().GetName().FullName; //this.remoteLoader = (RemoteLoader)this.appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName); Assembly assembly = this.appDomain.Load("ReadPrint"); Type t = assembly.GetType("ReadPrint.Program"); MethodInfo mi = t.GetMethod("DoSomething"); //mi.Invoke(Activator.CreateInstance(t), new object[] { }); var tmp = this.appDomain.GetAssemblies();
此處最為奇怪的是,盡管我們?cè)谏厦嬷付俗约?AppDomain 的 PrivateBinPath 和 配置文件,執(zhí)行時(shí)依然找的是主程序的 PrivateBinPath 和 配置文件,因此將執(zhí)行的那一行代碼注釋。
修改 Program.cs 中的代碼,改為僅調(diào)用 AssemblyDynamicLoader 的構(gòu)造函數(shù),其它不變,并查看主程序 AppDomain 中已加載的程序集:
AssemblyDynamicLoader adl = new AssemblyDynamicLoader(); //adl.remoteLoader.LoadAssembly(Path.Combine(modulesPath, "ReadPrint.exe")); //adl.remoteLoader.ExecuteMethod("ReadPrint.Program", "DoSomething"); //adl.Unload(); var tmp = AppDomain.CurrentDomain.GetAssemblies(); Console.ReadLine();
結(jié)果如圖所示:
2. 為什么要使用類似于代理的類 RemoteLoader, 而不直接使用 CreateInstanceAndUnwrap 創(chuàng)建加載進(jìn)來程序集的實(shí)例
直接使用會(huì)提示如下錯(cuò)誤:
需要注意的是,RemoteLoader 類繼承了 MarshalByRefObject,而繼承此類的應(yīng)用可以跨 AppDomain 使用。此處猜測(cè)雖然可以在主程序中創(chuàng)建新的 AppDomain,但新的 AppDomain 依然無法完全擺脫主程序。
我們不可能要求所有被調(diào)用的模塊都繼承此類,因此使用代理類 RemoteLoader。執(zhí)行的過程為:創(chuàng)建新的 AppDomain;在其中新建代理類 RemoteLoader,代理類幫助我們加載不同的模塊和依賴,并代替我們調(diào)用模塊。CreateInstanceAndUnwrap 實(shí)際上就是在新建的 AppDomain 中創(chuàng)建并實(shí)例化代理類,此后所有的工作均在新的 AppDomain 中進(jìn)行。
后記
代碼中使用了很多硬編碼。實(shí)際中,應(yīng)向主程序指出要調(diào)用的模塊路徑、依賴文件路徑和配置文件路徑,由主程序拷貝至臨時(shí)目錄,再使用 AssemblyDynamicLoader 創(chuàng)建新的 AppDomain 和執(zhí)行。
感覺大部分時(shí)候查看文章都是為了解決一些問題,因此本文把使用方法放在了前面,把詳細(xì)說明放在了后面,也算是一些優(yōu)化了XD。
以上就是C# 關(guān)于AppDomain的一些總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于C# AppDomain的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#數(shù)據(jù)結(jié)構(gòu)之順序表(SeqList)實(shí)例詳解
這篇文章主要介紹了C#數(shù)據(jù)結(jié)構(gòu)之順序表(SeqList)實(shí)現(xiàn)方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了順序表的定義、原理與具體實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11C#實(shí)現(xiàn)閃動(dòng)托盤圖標(biāo)效果的方法
這篇文章主要介紹了C#實(shí)現(xiàn)閃動(dòng)托盤圖標(biāo)效果的方法,涉及C# ImageList控件的使用技巧,需要的朋友可以參考下2016-06-06Unity實(shí)現(xiàn)答題系統(tǒng)的示例代碼
這篇文章主要和大家分享了利用Unity制作一個(gè)答題系統(tǒng)的示例代碼,文中的實(shí)現(xiàn)方法講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定的幫助,需要的可以參考一下2022-05-05C#的path.GetFullPath 獲取上級(jí)目錄實(shí)現(xiàn)方法
這篇文章主要介紹了C#的path.GetFullPath 獲取上級(jí)目錄實(shí)現(xiàn)方法,包含了具體的C#實(shí)現(xiàn)方法以及ASP.net與ASP等的方法對(duì)比,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10C#科學(xué)繪圖之使用scottPlot繪制多個(gè)圖像
ScottPlot是基于.Net的一款開源免費(fèi)的交互式可視化庫,支持Winform和WPF等UI框架,本文主要為大家詳細(xì)介紹了如何使用scottPlot實(shí)現(xiàn)繪制多個(gè)圖像,需要的可以參考下2023-12-12HttpWebRequest實(shí)現(xiàn)下載圖片至本地
這篇文章主要為大家詳細(xì)介紹了HttpWebRequest實(shí)現(xiàn)下載圖片至本地,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07C#中OpenCVSharp實(shí)現(xiàn)輪廓檢測(cè)
這篇文章主要介紹了C#中OpenCVSharp實(shí)現(xiàn)輪廓檢測(cè),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11