好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

NET插件系统——提升系统搜索插件和启动速度的思考

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. 依然有一定的性能提升的余地。   

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

   

完整的插件搜索器代码

  

当前标签: 插件

 

NET插件系统之四——提升系统搜索插件和启动速度的思考

 

.NET插件系统(三) 插件间通信问题——设计可自组织和注入的组装程序

 

.NET插件系统之二——不实例化获取插件信息和可视化方法

  

  

 

 

标签:  .NET ,  插件 ,  并行化

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于NET插件系统——提升系统搜索插件和启动速度的思考的详细内容...

  阅读:40次