Search code examples
c#.netwindows-servicesdynamic-loading

C# loading assemblies dynamically is very slow


I have a Windows services which is written in C# (.NET 3.5) and this service's role is to receive DLLs with a specific interface and execute their methods.

Some clients are complaining that sometimes the service fails to start and after I've looked at our logs I've noticed that the part when trying to load the assemblies dynamically is taking a lot of time (about ~30-40 seconds), so the service gets a timeout after 60 seconds when trying to start.

The major problem here is that this issue does not always occur. We can rarely recreate this slowness in QA Environment so when I'm trying to refactor the code of the dynamic loading I can't really know if I'm solving this issue.

Currently the code is loading 2 DLLs and for each assembly we get all referenced assemblies in order to dynamic load the assembly tree recursively.

I can also see when I'm going recursively on the loading I try to load .NET assemblies like System.Data, System.Core etc, which I obliviously fail because they aren't in my directory. This seems like a major time consuming. Any better suggestions?

/// <summary>
/// Loads all the assemblies and classes needed to run methods dinamically.
/// </summary>
public class BLService
{
  public static Dictionary<string, Assembly>    AssemblyHistory { get; private set; }

  static BLService()
  {
      // assembly
      Assembly assemblyToLoad = LoadAssembly(System.Configuration.ConfigurationManager.AppSettings[settingKey]);

      // load assembly dependencies
      foreach (var dependencyAssemblyName in assemblyToLoad.GetReferencedAssemblies())
          DynamicallyLoadAssembly(dependencyAssemblyName);
  }

  private static Assembly DynamicallyLoadAssembly(AssemblyName dependencyAssemblyName)
  {
      LCTracer.Instance.WriteToLog(string.Format("Loading assembly | Trying to dynamically load assembly {0}", dependencyAssemblyName.Name), LCTracer.ProfileAction.Start);
     Assembly currentAssembly = LoadAssembly(dependencyAssemblyName.Name);
     if (currentAssembly != null)
     {
         LCTracer.Instance.WriteToLog(string.Format("Loading assembly | Iterating on Assembly {0} references", dependencyAssemblyName.Name));
        foreach (var assembly in currentAssembly.GetReferencedAssemblies())
        {
           DynamicallyLoadAssembly(assembly);
        }
        LCTracer.Instance.WriteToLog(string.Format("Loading assembly | Finished iterating on Assembly {0} references", dependencyAssemblyName.Name));
      }
      LCTracer.Instance.WriteToLog(string.Format("Loading assembly"), LCTracer.ProfileAction.End);
     return currentAssembly;
  }

  /// <summary>
  /// Loads an assembly
  /// </summary>
  /// <param name="assemblyName"></param>
  /// <returns></returns>
  private static Assembly LoadAssembly(string assemblyName)
  {
     string assembliesDir = System.Configuration.ConfigurationManager.AppSettings["AssemblyPath"];
     string assemblyPath = Path.Combine(assembliesDir, assemblyName + ".dll");

     // We only load files from inside the designated directory.
     if (!File.Exists(assemblyPath))
     {
         LCTracer.Instance.WriteToLog("Loading assembly | " + assemblyName + " does not exist in path");
        return null;
     }

     byte[] assemblyByteArray = File.ReadAllBytes(assemblyPath);
     Assembly asm = null;

     try
     {
        asm = Assembly.Load(assemblyByteArray);

        // Load only if already loaded.
        if (!AssemblyHistory.ContainsKey(asm.GetName().Name))
        {
           AssemblyHistory.Add(asm.GetName().Name, asm);
           LCTracer.Instance.WriteToLog("Loading assembly | Success: Adding Assembly " + assemblyName + " to history.");
        }
     }
     catch (Exception ex)
     {
         LCTracer.Instance.WriteToLog("Loading assembly | Error: Adding Assembly " + assemblyName + " failed: message - " + ex.Message + " ,stack trace -  " + ex.StackTrace + " ,inner exception - " + ex.InnerException, null, LCTracer.LogDebugLevel.Low);

     }
     return (asm != null) ? AssemblyHistory[asm.GetName().Name] : AssemblyHistory[assemblyName];
  }
}

Edit: Added some code for context. You can think of this service as plugin runner, which gets a plugin and executes it. Any help will be appreciated.


Solution

  • I found the solution!

    One of the assembly that I try to load was NHibernate.XmlSerializers.dll which is created at runtime if not found and on slow machine this creation was taking a lot of time.

    Here is an article of how to pregenerated this dll to improve the performance.

    The main command is: sgen.exe NHibernate.dll /type:NHibernate.Cfg.MappingSchema.HbmMapping /compiler:/keyfile:NHibernate.snk