zoukankan      html  css  js  c++  java
  • 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 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)
                {
                    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;
                else
                    dllPluginFils = from file in mPluginFileList
                                    select 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 in
                                        type.GetCustomAttributes(typeof(XFrmWorkAttribute), false))  //获取该插件的XFrmWorkAttribute标识
                                    {
                                        XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;
    
                                        attr2.myType = type;  //将其类型赋值给XFrmWorkAttribute
    
    
                                        if (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. 依然有一定的性能提升的余地。   

      以下是我的插件搜索器的完整代码,欢迎有问题随时交流:

       

    完整的插件搜索器代码

      

    当前标签: 插件

     
    FerventDesert 2012-06-05 11:14 阅读:857 评论:5
     
    FerventDesert 2012-02-12 18:49 阅读:1817 评论:3

      

      

     
     
    标签: .NET插件并行化
  • 相关阅读:
    快速设置Scrapy随机的IP代理
    快速设置随机的UserAgent
    windows安装flask并创建虚拟环境
    数电和模电比较
    程序员如何写出一份好的文档?
    Qt简介
    基于 Qt的聊天工具
    如何将word组合图形保存成png
    2015电赛点滴
    函数定义
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2537070.html
Copyright © 2011-2022 走看看