Today I had an issue with assemblies in an AppDomain. What I tried to do was to create a new AppDomain, load assemblies into that AppDomain, analyze the types in these assemblies, and unload the AppDomain afterwards. Sounds like straight-forward, but I overlooked a little details and ended up with these assemblies being loaded into my main AppDomain and me not being able to unload them. So to begin with, here’s the some (faulty) code:
internal interface IAssemblyAnalyzer { string[] Analyze(string assemblyPath); } internal class AssemblyAnalyzer : MarshalByRefObject, IAssemblyAnalyzer { public string[] Analyze(string assemblyName) { var assembly = AppDomain.CurrentDomain.Load(assemblyName); return assembly.GetTypes().Select(t => t.FullName).ToArray(); } } internal class Program { private static void Main(string[] args) { var appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, new AppDomainSetup { ApplicationBase = @"c:\tmp", PrivateBinPath = @"c:\tmp", ShadowCopyFiles = "true" }); var type = typeof (AssemblyAnalyzer); var analyzer = (IAssemblyAnalyzer) appDomain.CreateInstanceFromAndUnwrap(type.Assembly.Location, type.FullName); var result = analyzer.Analyze("MunirHusseini.MysteriousAssembly"); Console.WriteLine(string.Join(@"\r\n", result)); AppDomain.Unload(appDomain); } }
In the code above, the assembly “MunirHusseini.MysteriousAssembly” is loaded into a new AppDomain. In that domain, all types inside the assembly are read-out and their names are returned. At the end, the AppDomain gets unloaded and with it all assemblies that were loaded into that assembly. Or don’t they? In my case, they didn’t. The assembly “MunirHusseini.MysteriousAssembly” that is located in “c:\tmp” will be locked until the process is shut down.
The trick here was to modify the AppDomainSetup and set the LoaderOptimization to LoaderOptimization.MultiDomainHost. This makes sure that no assemblies that are loaded into (or inside) the new AppDomain will be shared across AppDomain boundaries (except for assemblies that are in the GAC).
var appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, new AppDomainSetup { ApplicationBase = @"c:\tmp", PrivateBinPath = @"c:\tmp", ShadowCopyFiles = "true", LoaderOptimization = LoaderOptimization.MultiDomainHost });
So for best separation of AppDomains, do the following:
- Types called across AppDomain boundaries must inherit MarshalByRefObject.
- Types called across AppDomain boundaries must be called via an interface (IAssemblyAnalyzer in this sample).
- The property LoaderOptimization must be set to LoaderOptimization.MultiDomainHost.
Items 1 and 2 prevent that the type that is called across AppDomain boundaries will be loaded into the calling AppDomain, while item 3 prevents that any assemblies that are loaded from inside the new AppDomain will be loaded into the calling AppDomain.
Why use MultiDomainHost and not MultiDomain ? Thanks
//Application Domains and Assemblies
//https://msdn.microsoft.com/en-us/library/2bh4z9hs(v=vs.110).aspx
//There are three options for loading domain-neutral assemblies:
//•SingleDomain loads no assemblies as domain-neutral, except Mscorlib, which is always loaded domain-neutral. This setting is called single domain because it is commonly used when the host is running only a single application in the process.
//•MultiDomain loads all assemblies as domain-neutral. Use this setting when there are multiple application domains in the process, all of which run the same code.
//•MultiDomainHost loads strong-named assemblies as domain-neutral, if they and all their dependencies have been installed in the global assembly cache. Other assemblies are loaded and JIT-compiled separately for each application domain in which they are loaded, and thus can be unloaded from the process. Use this setting when running more than one application in the same process, or if you have a mixture of assemblies that are shared by many application domains and assemblies that need to be unloaded from the process.
From the same page you referenced above..
https://msdn.microsoft.com/en-us/library/2bh4z9hs(v=vs.110).aspx#Anchor_1
The way an assembly is loaded determines whether its just-in-time (JIT) compiled code can be shared by multiple application domains in the process, and whether the assembly can be unloaded from the process.
If an assembly is loaded domain-neutral, all application domains that share the same security grant set can share the same JIT-compiled code, which reduces the memory required by the application. However, the assembly CAN NEVER BE UNLOADED from the process.
If an assembly is not loaded domain-neutral, it must be JIT-compiled in every application domain in which it is loaded. However, the assembly CAN BE UNLOADED from the process by unloading all the application domains in which it is loaded.
___________________________________________________________
So if the developer wants to be able to unload managed assemblies then he/she must set LoaderOptimization to MultiDomainHost so that all assemblies are not loaded domain-neutral.
As per the documentation if LoaderOptimization to MultiDomain the assemblies can not be unloaded.
Hope that helps.