在目前的項目開發中,分佈式開發已經逐漸成爲主流。一個項目要是沒有采用分佈式架構,都很差意思跟別人說這是一個完整的項目。這句話雖然有些過激,可是隨着人們對效率的要求在提升,以及產品須要提高用戶體驗。只有在軟件項目的效率和體驗作到高質量,才能夠贏得用戶和市場。java
對於.NET項目,咱們使用較多的分佈式結構有Webservice,.Net remoting,MSMQ,WCF,WebAPI等等,咱們在使用這些框架的時候,從這些分佈式框架中獲得了很好的用戶體驗。在.NET項目中,分佈式架構對項目的開發也有很大的效率提高。安全
不少人會問,這些分佈式框架的底層原理是什麼呢?恐怕誰也不敢輕言幾句就能夠描述完畢,在這個博文系列中,就是簡單的描述一下這些分佈式結構的底層實現原理。架構
本文主要講解對象在應用程序域中的傳遞。主要講解應用程序域的一些核心對象,對於應用程序域的操做出現的比較少,因此在這裏給出的是程序集的一些基本操做。若有不足之處,還望多多指正。app
AppDomain在不少場合都是被翻譯爲「應用程序域」,在本文中也將採用這一翻譯。對於.NET的開發者,對於CLR應該是最熟悉不過了,CLR相似於java的JVM。在CLR中,AppDomain規定了代碼的執行範圍,提供了錯誤隔離的程度,提供了一個安全隔離度,而且擁有本身的資源。AppDomain的具體功能,有以下圖:框架
AppDomain相似與系統的進程,進程是有操做系統進行建立,AppDomain是由CLR進行建立。一個給定的AppDomain必須駐留在一個操做系統的進程中,而一個給定的進程能夠寄宿多個AppDomain。有以下圖:dom
如上圖所示,一個對象正好存放在一個AppDomain種,值也同樣。一個AppDomain中的對象引用必須是引用同一AppDomain中的對象,AppDomain的行爲就好像擁有本身私有的地址空間。若是兩個AppDomain須要使用一個類型,必須爲每一個AppDomain分別初始化和分配一次類型。必須爲各個用到類型的AppDomain分別加載和初始化一次類型的方法和程序集。進程種的各個AppDomain要維護類型的不一樣拷貝。對於類型的靜態子類,每一個AppDomain都有其本身的私有副本。分佈式
AppDomain的資源有如圖:ide
對於應用AppDomain的資源被加載,一直在內存中,卸載AppDomain資源是惟一卸載模塊或者程序集的途徑,卸載AppDomain資源也是回收類型靜態字段所佔內存的惟一方式。ui
在上面提到過操做系統的線程與AppDomain相似,在CLR中定義了System.Threading.Thread,在AppDomain中表示爲可調度的實體,在這裏提出一個新的概念,那就是「軟線程」和「硬線程」,顧名思義,操做系統的線程被稱爲「硬線程」,CLR中的System.Threading.Thread被稱爲「軟線程」。一個CLR軟線程對象駐留在一個肯定的AppDomain中;一個給定的AppDomain可能有多個軟線程對象。在當前的CLR中,對於給定的AppDomain,硬線程至多有一個軟線程對象屬於他,若是一個硬線程運行在多個AppDomain中,每一個AppDomain都會有一個明顯的軟線程對象屬於該線程。當給定的硬線程進入AppDomain後,就會獲得一樣的軟線程對象。this
上面介紹了一些AppDomain的基本概念,接下來咱們來簡單瞭解一下AppDomain的相關操做和核心對象。在.NET種能夠經過System.AppDomain類型訪問AppDomain。在這裏咱們具體瞭解一下System.AppDomain類型的方法和屬性。對於該類的說明:https://msdn.microsoft.com/en-us/library/system.appdomain(v=vs.110).aspx。
(1).CurrentDomain:獲取當前Thread 的當前應用程序域。
public static AppDomain CurrentDomain { get { return Thread.GetDomain(); } }
由以上代碼可知,該屬性爲一個靜態屬性,而且只有一個只讀屬性。該屬性只是簡單地提取存儲在硬線程的TLS(線程本地存儲區)中的AppDomain引用。你能夠在Thread.CurrentThread屬性中,從硬線程的TLS中提取當前的軟線程對象。
(2).GetData():爲指定名稱獲取存儲在當前應用程序域中的值。
[SecuritySafeCritical] public object GetData(string name) { if (name == null) throw new ArgumentNullException("name"); switch (AppDomainSetup.Locate(name)) { case -1: if (name.Equals(AppDomainSetup.LoaderOptimizationKey)) return (object) this.FusionStore.LoaderOptimization; object syncRoot = ((ICollection) this.LocalStore).SyncRoot; bool lockTaken = false; object[] objArray; try { Monitor.Enter(syncRoot, ref lockTaken); this.LocalStore.TryGetValue(name, out objArray); } finally { if (lockTaken) Monitor.Exit(syncRoot); } if (objArray == null) return (object) null; if (objArray[1] != null) ((IPermission) objArray[1]).Demand(); return objArray[0]; case 0: return (object) this.FusionStore.ApplicationBase; case 1: return (object) this.FusionStore.ConfigurationFile; case 2: return (object) this.FusionStore.DynamicBase; case 3: return (object) this.FusionStore.DeveloperPath; case 4: return (object) this.FusionStore.ApplicationName; case 5: return (object) this.FusionStore.PrivateBinPath; case 6: return (object) this.FusionStore.PrivateBinPathProbe; case 7: return (object) this.FusionStore.ShadowCopyDirectories; case 8: return (object) this.FusionStore.ShadowCopyFiles; case 9: return (object) this.FusionStore.CachePath; case 10: return (object) this.FusionStore.LicenseFile; case 11: return (object) (bool) (this.FusionStore.DisallowPublisherPolicy ? 1 : 0); case 12: return (object) (bool) (this.FusionStore.DisallowCodeDownload ? 1 : 0); case 13: return (object) (bool) (this.FusionStore.DisallowBindingRedirects ? 1 : 0); case 14: return (object) (bool) (this.FusionStore.DisallowApplicationBaseProbing ? 1 : 0); case 15: return (object) this.FusionStore.GetConfigurationBytes(); default: return (object) null; } }
每個AppDomain有本身的環境屬性集,能夠經過SetData和GetData方法訪問,在這裏給出了GetData()方法的源碼。該方法接收一個string參數,預約義應用程序域屬性的名稱,或已定義的應用程序域屬性的名稱。返回一個屬性的值,或 null(若是屬性不存在)。AppDomainSetup類爲一個封閉類,表示能夠添加到System.AppDomain的實例的程序集綁定信息。
(3).CreateDomain:使用指定的名稱、證據和應用程序域設置信息建立新的應用程序域。
[SecuritySafeCritical] [SecurityPermission(SecurityAction.Demand, ControlAppDomain = true)] public static AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info) { return AppDomain.InternalCreateDomain(friendlyName, securityInfo, info); }
該方法存在幾個重載,接收三個參數,域的友好名稱。friendlyName:此友好名稱可在用戶界面中顯示以標識域;securityInfo:肯定代碼標識的證據,該代碼在應用程序域中運行。傳遞 null 以使用當前應用程序域的證據。info:包含應用程序域初始化信息的對象。該方法返回一個新建立的應用程序域。
(4).ExecuteAssembly():使用指定的證據和實參執行指定文件中包含的程序集。
[Obsolete("Methods which use evidence to sandbox are obsolete and will be removed in a future release of the .NET Framework. Please use an overload of ExecuteAssembly which does not take an Evidence parameter. See http://go.microsoft.com/fwlink/?LinkID=155570 for more information.")] public int ExecuteAssembly(string assemblyFile, Evidence assemblySecurity, string[] args) { if (assemblySecurity != null && !this.IsLegacyCasPolicyEnabled) throw new NotSupportedException(Environment.GetResourceString("NotSupported_RequiresCasPolicyImplicit")); RuntimeAssembly assembly = (RuntimeAssembly) Assembly.LoadFrom(assemblyFile, assemblySecurity); if (args == null) args = new string[0]; return this.nExecuteAssembly(assembly, args); }
當建立一個AppDomain後,可使用一系列技術強制它加載和執行代碼,能夠採用ExecuteAssembly方法。該方法將目標AppDomain加載到程序集中,而且執行其主入口點。在父AppDomain種,ExecuteAssembly方法不會加載或者初始化指定的程序集。ExecuteAssembly是一個同步的例程,這就意味着調用者將被阻塞,直到程序的Main方法把控制權交還運行時。
ExecuteAssembly方法存在幾個重載版本,在這裏只拿出一個版原本說明。該方法接收三個參數,assemblyFile:包含要執行程序集的文件的名稱;assemblySecurity:爲程序集提供的證據;args:程序集的入口點的實參。該方法返回 程序集的入口點返回的值。該方法使用Assembly.LoadFrom來加載程序集。有關程序集的內容將在下一篇講解。
(5).DoCallBack():在另外一個應用程序域中執行代碼,該應用程序域由指定的委託標識。
public void DoCallBack(CrossAppDomainDelegate callBackDelegate) { if (callBackDelegate == null) throw new ArgumentNullException("callBackDelegate"); callBackDelegate(); }
這個指定方法必須是靜態的,而且它的簽名與CrossAppDomainDelegate簽名匹配。
using System; using System.Collections.Generic; using System.IO; using System.Reflection; namespace AppDomainToolkit { /// <summary> /// 用於肯定加載器應加載哪些加載上下文程序集。 /// </summary> public enum LoadMethod { /// <summary> /// 將程序集加載到LoadFrom上下文中,這將使程序集及其全部引用被發現 ///並加載到目標應用程序域中。 儘管它對DLL地獄的傾向,這多是去的方式 /// default,只要確保將應用程序的基本目錄傳遞給AssemblyResolver實例等 ///能夠正確解析引用。 這也容許同時加載同名的多個程序集 ///維護單獨的文件名。 這是推薦的方式。 /// </summary> LoadFrom, /// <summary> /// 使用原始文件名將組合件加載到內存中。 這將以匿名方式加載程序集,所以它不會有 ///一個加載上下文。 使用這個,若是你想要的位加載,但確保經過這個文件所在的目錄 /// AssemblyResolver實例,以便您能夠再次找到它。 這是相似於LoadFrom,除非你沒有獲得免費 ///經過融合查找已經存在的程序集名稱。 使用它能夠更好地控制彙編文件加載。 /// </summary> LoadFile, /// <summary> /// 使用原始文件名將目標程序集的位加載到內存中。 這本質上是一個動態組件 ///爲全部的CLR關心。 你將永遠不能找到這個與程序集解析器,因此不要使用這,除非你看 ///按名稱。 當心這一個。 /// </summary> LoadBits } /// <summary> /// 這個類將會把程序集加載到它加載到的任何應用程序域中。 這只是一個簡單的方便 /// wrapper環繞靜態Assembly.Load *方法,主要的好處是可以加載程序集 ///匿名按位。 當您以這種方式加載程序集時,不會有任何DLL文件的鎖定。 /// </summary> public class AssemblyLoader : MarshalByRefObject, IAssemblyLoader { #region Public Methods /// <inheritdoc /> /// <remarks> /// 若是此實例的LoadMethod設置爲LoadBits,而且PDB文件的路徑未指定,那麼咱們將嘗試猜想 ///到PDB的路徑並加載它。 注意,若是一個程序集被加載到內存中而沒有調試符號,那麼 /// image將被拋出。 警戒這個。 使用LoadBits方法加載程序集不會鎖定 /// DLL文件,由於整個程序集被加載到內存中而且文件句柄被關閉。 可是, ///以這種方式加載的程序集不會有與之關聯的位置,所以您必須鍵入程序集 ///它的強名。 當將同一程序集的多個版本加載到一個程序集時,這可能會致使問題 ///應用程序域。 /// </remarks> public Assembly LoadAssembly(LoadMethod loadMethod, string assemblyPath, string pdbPath = null) { Assembly assembly = null; switch (loadMethod) { case LoadMethod.LoadFrom: assembly = Assembly.LoadFrom(assemblyPath); break; case LoadMethod.LoadFile: assembly = Assembly.LoadFile(assemblyPath); break; case LoadMethod.LoadBits: // Attempt to load the PDB bits along with the assembly to avoid image exceptions. pdbPath = string.IsNullOrEmpty(pdbPath) ? Path.ChangeExtension(assemblyPath, "pdb") : pdbPath; // Only load the PDB if it exists--we may be dealing with a release assembly. if (File.Exists(pdbPath)) { assembly = Assembly.Load( File.ReadAllBytes(assemblyPath), File.ReadAllBytes(pdbPath)); } else { assembly = Assembly.Load(File.ReadAllBytes(assemblyPath)); } break; default: // In case we upadate the enum but forget to update this logic. throw new NotSupportedException("The target load method isn't supported!"); } return assembly; } /// <inheritdoc /> /// <remarks> /// 這個實現將執行目標程序集的盡力負載,它是必需的引用 ///進入當前應用程序域。 .NET框架在咱們容許使用的調用上鎖定咱們 ///當加載這些程序集時,因此咱們須要依賴於AssemblyResolver實例附加的 /// AppDomain爲了加載咱們想要的方式。 /// </remarks> public IList<Assembly> LoadAssemblyWithReferences(LoadMethod loadMethod, string assemblyPath) { var list = new List<Assembly>(); var assembly = this.LoadAssembly(loadMethod, assemblyPath); list.Add(assembly); foreach (var reference in assembly.GetReferencedAssemblies()) { list.Add(Assembly.Load(reference)); } return list; } /// <inheritdoc /> /// <remarks> /// Just a simple call to AppDomain.CurrentDomain.GetAssemblies(), nothing more. /// </remarks> public Assembly[] GetAssemblies() { return AppDomain.CurrentDomain.GetAssemblies(); } #endregion } }
本文主要講解了應用程序域的相關概念,本系列主要講解.NET對象的跨應用程序域的傳遞,因爲設計應用程序域的內容,因此本文主要講解了一些基本概念,以及一些基本的對象,對於應用程序域包含的程序集的相關內容將在下面進行操做。在實際的項目中,不多直接取操做應用程序域,比較多的是直接操做程序集,因此在本文的最後給出了一個就暗淡的程序集的操做方法。