[转]Using AppDomain to Load and Unload Dynamic Assemblies----work correctly
Recently a project I was working on required us to dynamically load an assembly at runtime, and invoke a method on the newly loaded assembly. After we had this working, I noticed that when we tried to then load a newer version of the dynamic assembly, the newer bits weren't being picked up, and old code was executing. Time to investigate a bit further:
I came across a great article by Jon Shemitz that pointed me in the right direction. AppDomains are a powerful concept in .NET, however as most applications you write live within a single AppDomain, you may not have noticed them before. My problem was that in .NET, you cannot simply unload an assembly directly. However, if we were to create a new AppDomain, load our dynamic assembly within it, call the methods needed, and then unload the AppDomain, we could load, unload, update versions and reload until our hearts content. The final piece of our puzzle (that we already had in place) is to use an interface to allow us the ability to invoke methods on the loaded assembly from our manager executable.
The solution I've created to demonstrate this concept uses four projects:
AppDomainLoad: A basic console application that simply news up an instance of the Manager class and calls the entry method.
WorkerShared: This project holds any objects that will be used by both the Manager and Worker classes. This is the only assembly that will be loaded by both AppDomains. Our shared interface, IWorker, lives in this project. A utility class calls AppDomain.CurrentDomain.GetAssemblies() to let us know what really is being loaded at runtime. This is a handy method to use, even in standard single AppDomain applications, to ensure you only load what you need.
WorkerManager: The .dll that creates a separate AppDomain, loads an instance of the dynamic assembly, and casts this instance to the IWorker interface. After all this, the Manager simply calls a method available from the interface. Once the work is complete, the AppDomain is unloaded, which drops any assemblies loaded within the worker domain.
DisconnectedWorker: Where the rubber hits the road, this project executes the actual work. The worker class a) implements the IWorker interface b) derives from MarshalByRefObject, which allows access to objects across domain boundaries (because these interactions are basically remoting under the covers), and c) is marked as Serializable.
One final note, after building the projects, I placed the WorkerShared.dll and DisconnectedWorker.dll bits into the C:\BlogProjects\AssemblyPool directory. This is only to simplify the project code; your project can locate these assemblies however you need. For this example, create the directory and drop the assemblies.
Here's the example code. The key classes' code is shown below. Let me know if you have any questions.
IWorker interface
{
public interface IWorker
{
// Define required methods:
void DoWork();
}
}
WorkerManager
using WorkerShared;
namespace WorkerManager
{
public class Manager
{
private const string CONFIG_ASSEMBLY_POOL = @"C:\BlogProjects\AssemblyPool";
private const string CONFIG_DYNAMIC_ASSEMBLY_PROJECT = "DisconnectedWorker";
private const string CONFIG_DYNAMIC_ASSEMBLY_FULLY_QUALIFIED_NAME = "DisconnectedWorker.Worker";
private const string FORMAT_WORKER_DOMAIN_FRIENDLY_NAME = "Dynamic Worker Domain";
private const string FORMAT_WORKER_DOMAIN_CREATED = "Created '{0}' AppDomain";
private const string FORMAT_WORKER_DOMAIN_UNLOADED = "Unloaded '{0}' AppDomain";
private const string FORMAT_WORK_COMPLETE = "All work complete.";
private const string FORMAT_START_ASSEMBLIES = "Starting Assemblies Loaded:";
private const string FORMAT_END_ASSEMBLIES = "Post-unload Assemblies Loaded:";
public void RunAppDomainExample()
{
// Show current assemblies before we start:
Console.WriteLine(FORMAT_START_ASSEMBLIES);
Utilities.WriteCurrentLoadedAssemblies();
// create display name for appDomain
string workerName = string.Format(FORMAT_WORKER_DOMAIN_FRIENDLY_NAME);
// Construct and setup appDomain settings:
AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationBase = CONFIG_ASSEMBLY_POOL;
ads.DisallowBindingRedirects = false;
ads.DisallowCodeDownload = true;
ads.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
// Create domain
Console.WriteLine();
AppDomain workerAppDomain = AppDomain.CreateDomain(workerName, null, ads);
Console.WriteLine(FORMAT_WORKER_DOMAIN_CREATED, workerName) ;
// do work on proxy
IWorker workerInstance =
(IWorker)
workerAppDomain.CreateInstanceAndUnwrap(CONFIG_DYNAMIC_ASSEMBLY_PROJECT,
&nbs p; CONFIG_D YNAMIC_ASSEMBLY_FULLY_QUALIFIED_NAME);
// Execute the task by invoking method on the interface instance
workerInstance.DoWork();
// Unload worker appDomain
AppDomain.Unload(workerAppDomain);
Console.WriteLine(FORMAT_WORKER_DOMAIN_UNLOADED, workerName) ;
Console.WriteLine();
// Show current assemblies before we start:
Console.WriteLine(FORMAT_END_ASSEMBLIES);
Utilities.WriteCurrentLoadedAssemblies();
Console.WriteLine(FORMAT_WORK_COMPLETE);
Console.ReadLine();
}
}
}
DisconnectedWorker
using WorkerShared;
namespace DisconnectedWorker
{
[Serializable]
public class Worker : MarshalByRefObject, IWorker
{
public void DoWork()
{
// Show the assemblies loaded in this appDomain
Utilities.WriteCurrentLoadedAssemblies();
}
}
}
Utilities
using System.Reflection;
namespace WorkerShared
{
public static class Utilities
{
public static void WriteCurrentLoadedAssemblies()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
Console.WriteLine("Loaded: {0}.", assembly.ManifestModule.Name);
}
}
}
}
Payday: The Project Output
Loaded: mscorlib.dll.
Loaded: Microsoft.VisualStudio.HostingProcess.Utilities.dll.
Loaded: System.Windows.Forms.dll.
Loaded: System.dll.
Loaded: System.Drawing.dll.
Loaded: Microsoft.VisualStudio.HostingProcess.Utilities.Sync.dll.
Loaded: AppDomainLoad.vshost.exe.
Loaded: System.Data.dll.
Loaded: System.Xml.dll.
Loaded: AppDomainLoad.exe.
Loaded: WorkerManager.dll.
Loaded: WorkerShared.dll.
Created 'Dynamic Worker Domain' AppDomain
Loaded: mscorlib.dll.
Loaded: Microsoft.VisualStudio.HostingProcess.Utilities.dll.
Loaded: DisconnectedWorker.dll.
Loaded: WorkerShared.dll.
Unloaded 'Dynamic Worker Domain' AppDomain
Post-unload Assemblies Loaded:
Loaded: mscorlib.dll.
Loaded: Microsoft.VisualStudio.HostingProcess.Utilities.dll.
Loaded: System.Windows.Forms.dll.
Loaded: System.dll.
Loaded: System.Drawing.dll.
Loaded: Microsoft.VisualStudio.HostingProcess.Utilities.Sync.dll.
Loaded: AppDomainLoad.vshost.exe.
Loaded: System.Data.dll.
Loaded: System.Xml.dll.
Loaded: AppDomainLoad.exe.
Loaded: WorkerManager.dll.
Loaded: WorkerShared.dll.
All work complete.