Loading

[转]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

namespace WorkerShared
{
    
public interface IWorker
    {
        
// Define required methods:
        
void DoWork();
    
}
}

WorkerManager

using System;
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 System;
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;
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

Starting 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.

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.
posted @ 2009-10-03 17:41  .net's  阅读(993)  评论(0编辑  收藏  举报