NET插件系统之四——提升系统搜索插件和启动速度的思考
一. 面临的问题
开发插件系统的主要优势是扩展性,我们不需要为系统模块的集成再多费脑筋,但这也带来了额外的问题。通常,系统需要在每次启动时搜索固定目录下的符合要求的插件。但是,当系统变得越来越庞大,所引用的dll文件越来越多时,就会出现很严重的问题:开启时间慢,性能差,用户体验降低,尤其是在调试程序时,会浪费大量宝贵的时间。
我确确实实的面临了这样的问题,有兴趣的读者可以看看我的插件系列文章的前几篇,这两天痛定思痛,决心提升系统搜索插件的性能。
我们先看一段普通的搜索插件的代码:
public void GetAllPluginInPath(string Path, string InterFaceName) var DllFileName = from file in Directory.GetFileSystemEntries(Path) where file.Contains(".dll") select file;造成启动慢的主要原因有:
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 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)
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;
foreach (Attribute attr in type.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) 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; else dllPluginFils = from file in mPluginFileList select folderLocation + file; //否则将插件文件名称拼接为完整的文件路径 Parallel.ForEach(dllPluginFils, file = Assembly assembly; assembly = Assembly.LoadFrom(file); catch return; 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 in type.GetCustomAttributes(typeof(XFrmWorkAttribute), false)) //获取该插件的XFrmWorkAttribute标识 XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute; attr2.myType = type; //将其类型赋值给XFrmWorkAttribute
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;
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()
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 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;
/// param name="InterFaceName" /param public static void GetAllPluginName(string folderLocation, bool 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;
foreach (Attribute attr in type.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);
/// 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) 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(); ;
IEnumerable string dllPluginFils; //最终要进行处理的的dll文件 if (mPluginFileList.Count == 0) //如果不存在插件文件目录,则获取该目录下所有dll文件 dllPluginFils = allDllFileList; else dllPluginFils = from file in mPluginFileList select folderLocation + file; //否则将插件文件名称拼接为完整的文件路径 Parallel.ForEach(dllPluginFils, file = Assembly assembly; assembly = Assembly.LoadFrom(file); catch return; 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 in type.GetCustomAttributes(typeof(XFrmWorkAttribute), false)) //获取该插件的XFrmWorkAttribute标识 XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute; attr2.myType = type; //将其类型赋值给XFrmWorkAttribute
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;
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();
foreach (Attribute attr in type.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)
foreach (Attribute attr in type.GetCustomAttributes(typeof(XFrmWorkAttribute), false)) XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute; tc.Add(attr2); mPluginDictionary.Add(interfaceName, tc); return tc;
.Net线程同步技术解读 C#开发者(面试者)都会遇到lock(Monitor),Mutex,Semaphore,SemaphoreSlim这四个与锁相关的C#类型,本文期望以最简洁明了的方式阐述四种对象的区别。
.Net Mirco Framework 2007技术大会 最近公司很多项目都有大量嵌入式设备使用,由于WinCE系统相对较大,对硬件平台要求过高,所以对.Net MF一直比较关注。今天总算大开眼界了
[.NET]使用十年股价对比各种序列化技术 原文:[.NET]使用十年股价对比各种序列化技术 1. 前言 上一家公司有搞股票,当时很任性地直接从服务器读取一个股票10年份的股价(还有各种指标)在客户端的图表上显示,而且因为是桌面客户端,传输的数据也是简单粗暴地使用Soap序列化。
【福州活动】| 福州首届.NET开源社区线下技术交流会 (2018.11.10) 阅读目录 【活动介绍】 微软爱开源,已是尽人皆知的事实。自从收购全球最大的开源社区 GitHub 之后,微软依旧使 GitHub 保持独立运营,并且通过此项举措,微软本身已经成为最大的社区服务者。
微服务定义及.Net Core中用的技术 它是一种架构模式,提倡将大的单体系统,按业务拆分成一个个较小且独立的服务,服务与服务之前进行相互协作和配合。 针对互联网行业的蓬勃发展,需要支撑的业务越来越多,越来越大,单体程序越来越难以支撑,因此才出现了微服务的这种架构。
相关文章
- 用.NET开发的磁力搜索引擎——btbook.net「建议收藏」
- asp.net HTTP Post使用Multipart_FormData方式上传内存数据到Nexus
- .net 温故知新:【9】.NET日志记录 ILogger使用和原理
- .Net 7 GC垃圾回收二叉树构建逻辑
- asp.net中DropDownList控件各种属性研究汇总
- [接上篇]在Window10/11的Linux子系统Docker上部署VB.NET Asp.Net Core WebAPI应用
- asp.net core 成为构建企业web应用首选
- (04).NET MAUI实战 MVVM
- 数据库使用.NET连接MySQL数据库(net连接mysql)
- .net搭建ASP.NET应用程序在Linux系统上(linux搭建asp)
- 开启.NETMySQL的无缝连接!(.net链接mysql)
- ASP.NET 下一代将全部开源并同时支持 Windows、Linux和Mac
- Oracle Database: The Ultimate Connection Guide for .NET Developers(net连接oracle)
- 使用.NET技术操作MySQL数据库:简单易学,高效稳定(.net操作mysql)
- Net与Oracle构建连接的数据价值(.net 链oracle)
- Net开发Oracle数据库新技术攻关挑战(.net开发oracle)
- 面向企业的Net框架开发与Oracle集成(net框架oracle)
- 数据库NET开发者操作Oracle数据库的全攻略(net操作oracle)
- Net框架如何使用MySQL数据库(.net能用mysql吗)
- 网上解决Net环境下MySQL数据库的同步问题(.net 同步mysql)
- NET与MySQL实现无缝连接(.net mysql连接)
- 用Net和MySQL实现软件开发编程(.net mysql编程)
- 分类NET 5与MySQL分类新加坡开发者技术展望(.net 5 mysql)
- asp.net根据汉字的拼音首字母搜索数据库(附LINQ调用方法)
- 基于.NET中:自动将请求参数绑定到ASPX、ASHX和MVC的方法(菜鸟必看)
- ASP.NET中application对象的使用介绍
- ASP.NET实现二维码(QRCode)的创建和读取实例
- .net实现序列化与反序列化实例解析