AppDomain and Unload

  • Thread starter Thread starter Wal Turner
  • Start date Start date
W

Wal Turner

Hi there. There are various snippets on forums regarding issues with
AppDomain.Unload and how it just doesnt work.

Fortunately, I got it working with the base case, after much fiddling.
Consider this 5 line program:

AppDomain domain = AppDomain.CreateDomain("MyDomain");
domain.CreateInstance("TempDLL", "TempDLL.Class1");
MessageBox.Show("try deleting file now"); //cant delete file
AppDomain.Unload(domain);
MessageBox.Show("try again"); //can delete file


Interstingly enough, if you use domain.Load( ) instead of createInstance you
can't delete the file as per the last line! (see
http://www.dotnet247.com/247reference/msgs/10/54067.aspx)

So getting around this was the first hurdle (also remember to use the name
of the dll WITHOUT the extension)

Now the task of putting it to work in the real application!

Unfortunately it didnt work - I got the the following errors when doing
AppDomain.Unload( )

An unhandled exception of type 'System.Threading.ThreadAbortException'
occurred in Unknown Module.
Additional information: Thread was being aborted.

(note these errors came out on console, they arent handle-able) - I'm not
sure that this would even prevent the module from unloading. So I'm not
convinced that this is the one error that is stopping the module being
unloaded. I have read that there are complications doing AppDomain.Unload( )
if you use API calls but i was thinking this shouldnt matter because I am
disposing of my main class and calling GC before unloading.

Any help

Wal
 
Hi Wal:

When you say you are putting to work in the real application, are you
using Unwrap on the ObjectHandle object returned by CreateInstance? As
soon as this happens the assembly loads into your first appdomain and
is un-loadable.
 
Scott, thanks for your reply.

I am indeed using Unwrap( ) to get a ref to the main class. I'm baffled as
to why this would be loaded into the first appdomain. It would seem logical
to load into the domain i just created!

Do you know where I can find more about this or suggest a way that it can be
worked around?

Regards

Wal
 
The problem is that you are loading the assembly into the context of the
default appdomain, not the remote appdomain that you just created. You need
to create a class that will proxy the loading/unloading of assemblies for
you and evaluate those assemblies entirely in the remote appdomain, and then
unload that appdomain.

As you pass a reference of the loaded assembly back to the calling appdomain
the runtime will load another copy of that assembly into that appdomain -
this will lock the file and prevent it from being deleted.

This link explains most of what you will need to know and provides sample
code too.
http://www.gotdotnet.com/team/clr/AppdomainFAQ.aspx

Dave
 
Hi Wal:

Well, when you Unwrap then the original app domain needs a definition
of the type in question - it needs all the same nitty gritty details
about the type's members and attributes that the second app domain
has.

There is some more info here:
http://www.gotdotnet.com/team/clr/AppdomainFAQ.aspx#_Toc514058497

Do you just need to be able to delete the files? You might be able to
just shadow copy the assembly.

There are more details in this post and in the comments:
http://blogs.msdn.com/jasonz/archive/2004/05/31/145105.aspx

and:
http://blogs.msdn.com/suzcook/archive/2003/07/08/57211.aspx
 
Dave thank you for your reply and link. Just in case you were wondering,
the underlying problem here is loading a DLL, then doing an update
(downloads to tmp file) then the download needs to overwrite the DLL.

Would you mind explainging what you mean by 'loading assembly into the
context of the default appdomain' ?
I am wondering at which line this would be occuring in the code. If i were
to create a class whose sole job was loading/unloading assemblies, i'm
unsure how it would be called from the newly created domain and not the
default domain. Basically I don't understand this unless what is meant is
that we replace

domain.CreateInstance("TempDLL", "TempDLL.Class1");

with

domain.CreateInstance("TempDLL", "TempDLL.ProxyClassThatLoadsClass1");
?
 
inline

Wal Turner said:
Dave thank you for your reply and link. Just in case you were wondering,
the underlying problem here is loading a DLL, then doing an update
(downloads to tmp file) then the download needs to overwrite the DLL.

Would you mind explainging what you mean by 'loading assembly into the
context of the default appdomain' ?

Basically, whenever you do this...

Assembly a = remoteDomain.Load(xxx);

it loads the assembly into both appdomains - the remote one directly when
the .Load executes, and the calling appdomain when a reference to the loaded
assembly is returned to the calling appdomain.

Whenever you reference an object as a concrete type the runtime has to load
the metadata for that type into the current appdomain, so even if the object
instance is remoted the metadata for that object is still loaded into the
appdomain.

I am wondering at which line this would be occuring in the code. If i were
to create a class whose sole job was loading/unloading assemblies, i'm
unsure how it would be called from the newly created domain and not the
default domain. Basically I don't understand this unless what is meant is
that we replace

domain.CreateInstance("TempDLL", "TempDLL.Class1");

with

domain.CreateInstance("TempDLL", "TempDLL.ProxyClassThatLoadsClass1");
?

When you create an instance and then access the object it has to load the
metadata. which causes the assembly to get loaded into both appdomains.

One way you can handle this is something like this...

void DoSomeWorkFromCallingAppdomain()
{ // this is the routine you want to do the work.
AppDomain remoteDomain = AppDomain.CreateDomain(...);
RemoteHelper helper =
(RemoteHelper)remoteDomain.CreateInstanceFromAndUnwrap(...);
helper.DoSomeWork();
AppDomain.Unload(remoteDomain);
}

class RemoteHelper : MarshalByRefObj
{
public string DoSomeWork()
{
// create other objects in other assemblies, do work, and return .
Assembly a = Assembly.Load("AsmToBeUnloadedLater");
return "SomeKindOfResult";
}
}

The class RemoteHelper can be in the same assembly as the method
DoSomeWorkFromCallingAppdomain. This routine accesses the types and
assemblies that you want to unload later on. Basically you have to isolate
all the types and only access the ones you want to unload from within
methods in class RemoteHelper. The data returned from the remoted method
must not be defined in the assemblies you want to unload.

Another possibility (one I have not tried yet) is to use Shadow copying to
allow you to overwrite the assembly without needing to unload the assembly.
You wont be able to load the new assembly into the appdomain until you
recycle the appdomain (which may be a problem for you default appdomain) but
you can at least overwrite the bits.
 
Back
Top