NET插件系统之四——提升系统搜索插件和启动速度的思考
?
一. 面臨的問題
開發插件系統的主要優勢是擴展性,我們不需要為系統模塊的集成再多費腦筋,但這也帶來了額外的問題。通常,系統需要在每次啟動時搜索固定目錄下的符合要求的插件。但是,當系統變得越來越龐大,所引用的dll文件越來越多時,就會出現很嚴重的問題:開啟時間慢,性能差,用戶體驗降低,尤其是在調試程序時,會浪費大量寶貴的時間。
我確確實實的面臨了這樣的問題,有興趣的讀者可以看看我的插件系列文章的前幾篇,這兩天痛定思痛,決心提升系統搜索插件的性能。
我們先看一段普通的搜索插件的代碼:
?
1 public void GetAllPluginInPath(string Path, string InterFaceName) 2 { 3 var DllFileName = from file in Directory.GetFileSystemEntries(Path) 4 where file.Contains(".dll") 5 select file; 6 7 8 9 //string[] DllFileName = Directory.GetFileSystemEntries(Path); 10 Type[] types; 11 foreach (string file in DllFileName) 12 { 13 14 if (System.IO.Path.GetExtension(file) == ".dll") 15 { 16 Assembly assembly; 17 18 try 19 { 20 assembly = Assembly.LoadFrom(file); 21 } 22 catch 23 { 24 continue; 25 } 26 27 try 28 { 29 types = assembly.GetTypes(); 30 } 31 catch (Exception ex) 32 { 33 continue; 34 } 35 36 foreach (Type type in types) 37 { 38 if (type.GetInterface(InterFaceName) != null && !type.IsAbstract) 39 { 40 object thisObject = Activator.CreateInstance(type); 41 42 43 IXPlugin rc1 = thisObject as IXPlugin; 44 45 46 //如果要在啟動時被加載 47 if (rc1 != null && rc1.isStartLoaded) 48 { 49 AddPlugin(rc1); 50 } 51 } 52 } 53 } 54 } 55 }?
造成啟動慢的主要原因有:
1. 目錄下包含大量dll文件(這是因為項目引用了大量第三方庫),它們并不包含我們開發的組件,卻白白浪費大量搜索時間。有些dll文件不是托管dll,在獲取程序集時還會拋出異常,直接捕獲后,也會造成時間損失。
2. 上述代碼僅搜索了滿足一種接口規范的插件, (見函數的參數InterFaceName)。如果不止一種插件類型,那么可能要做很多次這樣的查找,對性能的影響更大。
????? 3. 為了獲取插件的一些信息(比如是否要在啟動時加載),不得不實例化其對象獲取字段,這種性能損失也是不能承受的。
二. 解決途徑
???? 找到問題,我們對癥下藥:
1.成熟的軟件系統采用了插件樹的機制,將插件存儲為樹結構,包含父子關系,這樣能盡可能的提升搜索和加載性能,同時方便管理,比如Ecilpse。 但是,這種復雜的插件管理機制可能不適用于我們開發的輕量級系統,因此我們僅僅考慮扁平化的插件結構。
2. 雖然插件的數量是經常變化的,但通常加載的dll文件種類很少變化。我們可以考慮把實際包含所需插件的dll文件名列表存儲起來,從而在搜索時僅搜索這些固定的dll文件,提升性能。
3. 插件的種類可能多種多樣,所以我們希望能一次性獲得全部類型的插件。
4. 采用.NET4.0的并行庫機制實現插件的并行搜索。
三.?插件結構表述
該部分已經在我的插件系列文章的.NET插件系統之二——不實例化獲取插件信息和可視化方法?中進行了描述,主要是標記接口名稱的InterfaceAttribute 和 標記實現接口的插件的XFrmWorkAttribute,你需要在插件接口和插件實現的類上添加這兩類標識,此處不再贅述。
我們定義兩個數據結構存儲插件名稱和插件信息:
????
/// <summary>/// 為了便于序列化而簡化的插件接口數據類型,是簡化的InterfaceAttribute/// </summary> [Serializable]public class PluginNameLite{public string myName { get; set; }public SearchStrategy mySearchStrategy { get; set; }public string detailInfo { get; set; }public PluginNameLite(){}public PluginNameLite(InterfaceAttribute attr){myName = attr.myName;mySearchStrategy = attr.mySearchStrategy;detailInfo = attr.DetailInfo;}}/// <summary>/// 插件集合/// </summary>public class PluginCollection : ObservableCollection<XFrmWorkAttribute>{public PluginCollection(): base(){}/// <summary>/// 可以被序列化的簡化插件字典,僅包含插件接口名稱和搜索策略/// </summary>static List<PluginNameLite> myPluginNameList = new List<PluginNameLite>();/// <summary>/// 插件字典/// </summary>static Dictionary<Type, PluginCollection> mPluginDictionary = new Dictionary<Type, PluginCollection>();?
四. 插件搜索的方法
我們將插件搜索的步驟分為兩步:
1. 搜索所有接口契約(即搜索所有的接口)
/// <summary>/// 獲取所有的插件接口契約名稱/// </summary>/// <param name="Path"></param>/// <param name="InterFaceName"></param>public static void GetAllPluginName(string folderLocation, bool isRecursiveDirectory){List<PluginNameLite> mPluginNameList = new List<PluginNameLite>(); //緩存所有插件名稱if (!isRecursiveDirectory) {try //如果不執行遞歸搜索,則查看在目錄下是否有保存了插件名稱的文件,若有,直接反序列化之,不執行插件名稱搜索 { mPluginNameList = CustomSerializer.Deserialize<List<PluginNameLite>>(folderLocation + "\\PluginLog.xml"); myPluginNameList.AddRange(mPluginNameList);return;}catch (Exception ex){}}var DllFile = from file in Directory.GetFileSystemEntries(folderLocation) //若無緩存文件,獲取目錄下全部的dll文件執行搜索where file.Contains(".dll")select file;Parallel.ForEach(DllFile, //并行化處理file =>{Type[] types;Assembly assembly;try{assembly = Assembly.LoadFrom(file);}catch{return;}try{types = assembly.GetTypes();}catch{return;}foreach (Type type in types){if (type.IsInterface == false)continue;// Iterate through all the Attributes for each method.foreach (Attribute attr intype.GetCustomAttributes(typeof(InterfaceAttribute), false)){mPluginNameList.Add(new PluginNameLite(attr as InterfaceAttribute));}}});if (isRecursiveDirectory) ////執行遞歸搜索 {foreach (var dir in Directory.GetDirectories(folderLocation)){GetAllPluginName(dir, isRecursiveDirectory);}}else //保存當前目錄下的插件名稱 {CustomSerializer.Serialize(mPluginNameList, folderLocation + "\\PluginLog.xml");myPluginNameList.AddRange(mPluginNameList);}}
??? 流程圖如下:
?
2. 搜索所有實現接口契約的插件
? ?直接用代碼說話
/// <summary>/// 獲取所有插件/// </summary>/// <param name="folderLocation"></param>/// <param name="isRecursiveDirectory">是否進行目錄遞歸搜索</param>public static void GetAllPlugins(string folderLocation, bool isRecursiveDirectory){bool isSaved = false; //是否已經保存了包含插件的dll文件列表List<string> mPluginFileList = new List<string>(); //包含插件的dll文件列表List<string> allDllFileList = new List<string>(); //所有dll文件列表if (!isRecursiveDirectory){try{mPluginFileList = CustomSerializer.Deserialize<List<string>>(folderLocation + "\\PluginFileLog.xml");isSaved = true;}catch (Exception ex){allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)where file.Contains(".dll")select file).ToList();}}else{allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)where file.Contains(".dll")select file).ToList(); ;}Type[] types;IEnumerable<string> dllPluginFils; //最終要進行處理的的dll文件if (mPluginFileList.Count == 0) //如果不存在插件文件目錄,則獲取該目錄下所有dll文件dllPluginFils = allDllFileList;elsedllPluginFils = from file in mPluginFileListselect folderLocation + file; //否則將插件文件名稱拼接為完整的文件路徑 Parallel.ForEach(dllPluginFils,file =>{Assembly assembly;try{assembly = Assembly.LoadFrom(file);}catch{return;}try{types = assembly.GetTypes();}catch{return;}foreach (Type type in types){if (type.IsInterface == true)continue;Type interfaceType = null;string interfaceName = null;foreach (var interfacename in myPluginNameList) //對該Type,依次查看是否實現了插件名稱列表中的接口 {interfaceType = type.GetInterface(interfacename.myName);if (interfaceType != null){interfaceName = interfacename.myName;// Iterate through all the Attributes for each method.foreach (Attribute attr intype.GetCustomAttributes(typeof(XFrmWorkAttribute), false)) //獲取該插件的XFrmWorkAttribute標識 {XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;attr2.myType = type; //將其類型賦值給XFrmWorkAttributeif (attr2.MainKind != interfaceName){continue;}PluginCollection pluginInfo = null; //保存到插件字典當中if (mPluginDictionary.TryGetValue(interfaceType, out pluginInfo)){pluginInfo.Add(attr2); //若插件字典中已包含了該interfaceType的鍵,則直接添加 }else{var collection = new PluginCollection();collection.Add(attr2);mPluginDictionary.Add(interfaceType, collection); //否則新建一項并添加之 }file = file.Replace(folderLocation, ""); //獲取文件在該文件夾下的真實名稱if (!mPluginFileList.Contains(file)) //若插件文件列表中不包含此文件則添加到文件目錄中 mPluginFileList.Add(file);goto FINISH;}}}FINISH:;}});if (isRecursiveDirectory) //執行遞歸搜索 {foreach (var dir in Directory.GetDirectories(folderLocation)){GetAllPlugins(dir, isRecursiveDirectory);}}else{if (!isSaved) //若沒有保存插件文件目錄,則反序列化保存之。CustomSerializer.Serialize(mPluginFileList, folderLocation + "\\PluginFileLog.xml");}}??? 由于篇幅有限,搜索插件的流程與搜索插件名稱的流程基本相同,因此省略流程圖。
3. 并行化優化
讀者可以看到,在搜索不同dll文件的插件時 ,使用了?Parallel.ForEach ,網上介紹該方法的文章很多,此處不再贅述。 同時,系統直接將所有插件一次性的搜索完成。大幅度的提升了搜索速度。
????
五. 總結和問題
總結:
1. 系統盡可能的減少了對插件本身的限制,通過添加attribute的方式即可標記插件,減少了對原生代碼的修改。
2. 實測表明,文件目錄下存在60個左右的dll文件,其中只有6個是作者完成的包含插件的文件,?在I7 2600K的電腦上:(Debug版本)
原始版本的插件搜索和實例化需要將近5s的啟動時間
????????? 通過緩存文件目錄和插件目錄,時間減少2.7s
? ?? 通過并行化搜索dll文件下的插件,時間進一步減少1s
?????? ?最終,啟動時間僅僅為1.3s左右,同時還避免了多次搜索插件
???? 存在的問題:
1. 插件系統可以自動檢測出同一dll文件中插件的變化,但在緩存了dll文件名之后,是無法自動檢測出dll文件的變化的。這種情況下,需要首先刪除記錄插件名稱和文件的緩存xml文件才能檢測出來。
2. 依然有一定的性能提升的余地。
以下是我的插件搜索器的完整代碼,歡迎有問題隨時交流:
???
完整的插件搜索器代碼 using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks;namespace XFrmWork.Data {/// <summary>/// 插件集合/// </summary>public class PluginCollection : ObservableCollection<XFrmWorkAttribute>{public PluginCollection(): base(){}}/// <summary>/// 執行搜索策略/// </summary>public enum SearchStrategy{/// <summary>/// 目錄內搜索/// </summary> FolderSearch,/// <summary>/// 目錄內遞歸搜索/// </summary> RecursiveFolderSearch,}/// <summary>/// 該類定義了插件系統的接口契約記錄/// </summary>public class InterfaceAttribute : Attribute{/// <summary>/// 該插件接口的名稱/// </summary>public string myName { get; set; }/// <summary>/// 搜索策略/// </summary>public SearchStrategy mySearchStrategy { get; set; }/// <summary>/// 相關信息/// </summary>public string DetailInfo { get; set; }public InterfaceAttribute(string thisName, string thisDetailInfo, SearchStrategy thisSearchStrategy)// 定位參數 {this.myName = thisName;this.DetailInfo = thisDetailInfo;this.mySearchStrategy = thisSearchStrategy;}}/// <summary>/// 為了便于序列化而簡化的插件接口數據類型,是簡化的InterfaceAttribute/// </summary> [Serializable]public class PluginNameLite{public string myName { get; set; }public SearchStrategy mySearchStrategy { get; set; }public string detailInfo { get; set; }public PluginNameLite(){}public PluginNameLite(InterfaceAttribute attr){myName = attr.myName;mySearchStrategy = attr.mySearchStrategy;detailInfo = attr.DetailInfo;}}/// <summary>/// 單例模式提供的插件搜索器/// </summary>public class PluginProvider{PluginProvider(){}/// <summary>/// 可以被序列化的簡化插件字典,僅包含插件接口名稱和搜索策略/// </summary>static List<PluginNameLite> myPluginNameList = new List<PluginNameLite>();/// <summary>/// 插件字典/// </summary>static Dictionary<Type, PluginCollection> mPluginDictionary = new Dictionary<Type, PluginCollection>();/// <summary>/// 獲取某插件在插件目錄中的索引號/// </summary>/// <param name="interfaceName">接口名稱</param>/// <param name="className">類名</param>/// <returns></returns>public static int GetObjectIndex(Type interfaceName, Type className){foreach (var rc in GetPluginCollection(interfaceName)){if (rc.myType == className)return GetPluginCollection(interfaceName).IndexOf(rc);}return 100;}/// <summary>/// 獲取所有的插件接口契約名稱/// </summary>/// <param name="Path"></param>/// <param name="InterFaceName"></param>public static void GetAllPluginName(string folderLocation, bool isRecursiveDirectory){List<PluginNameLite> mPluginNameList = new List<PluginNameLite>(); //緩存所有插件名稱if (!isRecursiveDirectory){try //如果不執行遞歸搜索,則查看在目錄下是否有保存了插件名稱的文件,若有,直接反序列化之,不執行插件名稱搜索 {mPluginNameList = CustomSerializer.Deserialize<List<PluginNameLite>>(folderLocation + "\\PluginLog.xml");myPluginNameList.AddRange(mPluginNameList);return;}catch (Exception ex){}}var DllFile = from file in Directory.GetFileSystemEntries(folderLocation) //若無緩存文件,獲取目錄下全部的dll文件執行搜索where file.Contains(".dll")select file;Parallel.ForEach(DllFile, //并行化處理file =>{Type[] types;Assembly assembly;try{assembly = Assembly.LoadFrom(file);}catch{return;}try{types = assembly.GetTypes();}catch{return;}foreach (Type type in types){if (type.IsInterface == false)continue;// Iterate through all the Attributes for each method.foreach (Attribute attr intype.GetCustomAttributes(typeof(InterfaceAttribute), false)){mPluginNameList.Add(new PluginNameLite(attr as InterfaceAttribute));}}});if (isRecursiveDirectory) ////執行遞歸搜索 {foreach (var dir in Directory.GetDirectories(folderLocation)){GetAllPluginName(dir, isRecursiveDirectory);}}else //保存當前目錄下的插件名稱 {CustomSerializer.Serialize(mPluginNameList, folderLocation + "\\PluginLog.xml");myPluginNameList.AddRange(mPluginNameList);}}/// <summary>/// 獲取所有插件/// </summary>/// <param name="folderLocation"></param>/// <param name="isRecursiveDirectory">是否進行目錄遞歸搜索</param>public static void GetAllPlugins(string folderLocation, bool isRecursiveDirectory){bool isSaved = false; //是否已經保存了包含插件的dll文件列表List<string> mPluginFileList = new List<string>(); //包含插件的dll文件列表List<string> allDllFileList = new List<string>(); //所有dll文件列表if (!isRecursiveDirectory){try{mPluginFileList = CustomSerializer.Deserialize<List<string>>(folderLocation + "\\PluginFileLog.xml");isSaved = true;}catch (Exception ex){allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)where file.Contains(".dll")select file).ToList();}}else{allDllFileList = (from file in Directory.GetFileSystemEntries(folderLocation)where file.Contains(".dll")select file).ToList(); ;}Type[] types;IEnumerable<string> dllPluginFils; //最終要進行處理的的dll文件if (mPluginFileList.Count == 0) //如果不存在插件文件目錄,則獲取該目錄下所有dll文件dllPluginFils = allDllFileList;elsedllPluginFils = from file in mPluginFileListselect folderLocation + file; //否則將插件文件名稱拼接為完整的文件路徑 Parallel.ForEach(dllPluginFils,file =>{Assembly assembly;try{assembly = Assembly.LoadFrom(file);}catch{return;}try{types = assembly.GetTypes();}catch{return;}foreach (Type type in types){if (type.IsInterface == true)continue;Type interfaceType = null;string interfaceName = null;foreach (var interfacename in myPluginNameList) //對該Type,依次查看是否實現了插件名稱列表中的接口 {interfaceType = type.GetInterface(interfacename.myName);if (interfaceType != null){interfaceName = interfacename.myName;// Iterate through all the Attributes for each method.foreach (Attribute attr intype.GetCustomAttributes(typeof(XFrmWorkAttribute), false)) //獲取該插件的XFrmWorkAttribute標識 {XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;attr2.myType = type; //將其類型賦值給XFrmWorkAttributeif (attr2.MainKind != interfaceName){continue;}PluginCollection pluginInfo = null; //保存到插件字典當中if (mPluginDictionary.TryGetValue(interfaceType, out pluginInfo)){pluginInfo.Add(attr2); //若插件字典中已包含了該interfaceType的鍵,則直接添加 }else{var collection = new PluginCollection();collection.Add(attr2);mPluginDictionary.Add(interfaceType, collection); //否則新建一項并添加之 }file = file.Replace(folderLocation, ""); //獲取文件在該文件夾下的真實名稱if (!mPluginFileList.Contains(file)) //若插件文件列表中不包含此文件則添加到文件目錄中 mPluginFileList.Add(file);goto FINISH;}}}FINISH:;}});if (isRecursiveDirectory) //執行遞歸搜索 {foreach (var dir in Directory.GetDirectories(folderLocation)){GetAllPlugins(dir, isRecursiveDirectory);}}else{if (!isSaved) //若沒有保存插件文件目錄,則反序列化保存之。CustomSerializer.Serialize(mPluginFileList, folderLocation + "\\PluginFileLog.xml");}}/// <summary>/// 獲取接口中固定索引類型的實例/// </summary>/// <param name="interfaceName">接口名稱</param>/// <param name="index">索引號</param>/// <returns>實例化的引用</returns>public static Object GetObjectInstance(Type interfaceName, int index){return Activator.CreateInstance(GetPluginCollection(interfaceName)[index].myType);}/// <summary>/// 獲取某程序集的接口列表/// </summary>/// <param name="interfaceName"></param>/// <param name="myAssembly"></param>/// <returns></returns>public static ObservableCollection<XFrmWorkAttribute> GetPluginCollection(Type interfaceName, Assembly myAssembly){return GetPluginCollection(interfaceName, myAssembly, false);}public static ObservableCollection<XFrmWorkAttribute> GetPluginCollection(Type interfaceName, Assembly myAssembly, bool isAbstractRequired){if (mPluginDictionary.ContainsKey(interfaceName))return mPluginDictionary[interfaceName];else //若發現插件不存在,則再執行搜索(這種可能性很低) {PluginCollection tc = new PluginCollection();Type[] types = myAssembly.GetTypes();foreach (Type type in types){if (type.GetInterface(interfaceName.ToString()) != null){if (!isAbstractRequired && type.IsAbstract == true){continue;}// Iterate through all the Attributes for each method.foreach (Attribute attr intype.GetCustomAttributes(typeof(XFrmWorkAttribute), false)){XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;tc.Add(attr2);}}}mPluginDictionary.Add(interfaceName, tc);return tc;}}public static ObservableCollection<XFrmWorkAttribute> GetPluginCollection(Type interfaceName){if (mPluginDictionary.ContainsKey(interfaceName))return mPluginDictionary[interfaceName];else{PluginCollection tc = new PluginCollection();Assembly assembly = Assembly.GetAssembly(interfaceName);Type[] types = assembly.GetTypes();foreach (Type type in types){if (type.GetInterface(interfaceName.ToString()) != null && !type.IsAbstract){// Iterate through all the Attributes for each method.foreach (Attribute attr intype.GetCustomAttributes(typeof(XFrmWorkAttribute), false)){XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;tc.Add(attr2);}}}mPluginDictionary.Add(interfaceName, tc);return tc;}}}}?
轉載于:https://www.cnblogs.com/buptzym/archive/2012/06/05/2534703.html
總結
以上是生活随笔為你收集整理的NET插件系统之四——提升系统搜索插件和启动速度的思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Iframe自适应高度绝对好使的代码 兼
- 下一篇: 百度地图API之MyLocationOv