C# loading assemblies dynamically is very slow

3.4k views Asked by At

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.

2

There are 2 answers

0
Danel Wolloch On BEST ANSWER

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

1
ShayD On

Try installing Fusion log viewer (comes with Windows SDK) on the complaining clients' servers, and set it to log all load attempts (also successfull). Then you can see where it si looking for the assemblies - you might find some slow network path there or other problems.

MSDN on Fusion Log Viewer

usefull blog post on using Fusion Log Viewer