Unloading Application Domains
As described, unloading code from a process requires you to unload the application domain in which the code is running. The CLR currently doesn't support unloading individual assemblies or types. There are several scenarios in which an application might want to unload a domain. One of the most common reasons for wanting to unload an application domain is to reduce the working set of a process. This is
Application domains can be unloaded by calling a static method on System.AppDomain called Unload . AppDomain.Unload takes one parameteran instance of System.AppDomain representing the domain you want to unload. Application domains can also be unloaded from unmanaged code using the CLR hosting interfaces. ICLRRuntimeHost includes a method called UnloadAppDomain that takes the unique numerical identifier of the application domain you want to unload.
Calls to
AppDomain.Unload
or
ICLRRuntimeHost::UnloadAppDomain
cause the CLR to unload the application domain gracefully. By
gracefully
, I mean that the CLR unloads the domain in an orderly fashion that lets the application code currently running in the domain reach a natural, predictable endpoint. Specifically, the following sequence of events occurs during a
These steps are described in the following sections. Step 1: Aborting the Threads Running in the Domain
The CLR begins the process of unloading an application domain by stopping all code that is currently executing in the domain. Threads running in the domain are given the opportunity to complete gracefully rather than being abruptly
Code that is running on a thread destined to be aborted has two opportunities to clean up. First, the CLR runs code in all finally blocks as part of thread termination. Also, the finalizers for all objects are run (see the "Step 3: Running Finalizers" section).
Be aware that a
ThreadAbortException
can be thrown on threads that are currently not even executing in the partially unloaded application domain. If a thread executed in that domain at one point and must return to that domain as the call stack is being unwound, that thread receives a
ThreadAbortException
. For example, consider the case in which a thread is executing some code in Domain 1. At some later point in time, the thread
Figure 5-9. A thread's stack on a cross-domain call
Clearly, aborting this thread is necessary because the stack contains addresses of calls that will be invalid after the domain is completely unloaded. Step 2: Raising an Unload EventAfter the ThreadAbortException s have been thrown and the finally blocks have been run, the CLR raises an event to indicate the domain is unloading. This event can be received in either managed code or unmanaged code. I talk more about how to catch this event later in the chapter. Step 3: Running FinalizersEarlier in the unloading process, the CLR ran all finally clauses as one way to enable the threads running in the domain to clean up before being aborted. In addition to running finally s, the CLR finalizes all objects that live in the domain. This gives the objects a final opportunity to free all resources allocated while the domain was active.
Typically, finalizers are run as a part of a garbage collection. When an object is being collected, all unused objects that reference it are likely being finalized and collected as well. However, the order in which objects are finalized is less well defined when the application domain in which an object lives is being unloaded. As a result, it might be necessary for an object's finalizer to behave differently depending on whether the object is being finalized because the domain is unloaded or as part of a collection. A finalizer can tell the difference between these two cases by calling the
IsFinalizingForUnload
method on
System.AppDomain
. As its
Clearly, running finalizers and
finally
s during unload enables code contained in add-ins to be run. By default, the CLR makes no
As you've seen, the CLR does its best to enable cleanup code to be run as part of unloading an application domain. Although this helps provide for a clean shutdown, the application's logic is terminated the instant the ThreadAbortException is thrown. As a result, the work the application was doing might be halted prematurely. In the best case, this simply results in a program that safely stops running early. However, it's easy to imagine scenarios in which the premature termination of the application leaves the system in an inconsistent state. As the author of an application that initiates application domain unloads, it's in your best interest to unload a domain only when no active threads are running in the domain. This helps you minimize the times when unloading a domain adversely affects the application. The CLR provides no APIs or other mechanisms to help you determine when a domain is empty. You must build additional logic into your application to determine when requests you've dispatched to different threads have been completed. When they all complete successfully, you know the domain is safe to unload. Step 4: Freeing the Internal CLR Data Structures
After all finalizers have run and no more threads are executing in the domain, the CLR is ready to unload all the in-memory data structures used in the internal implementation. Before this happens, however, the objects that resided in the domain must be collected. After the
Exceptions Related to Unloading Application DomainsYou should be aware of a few exceptions that might get thrown as a result of unloading an application domain. First, once a domain is unloaded, access to objects that used to live in that domain is illegal. When an attempt to access such an object occurs, the CLR will throw an ApplicationDomainUnloadedException . Second, there are a few cases in which unloading an application domain is invalid. For example, AppDomain.Unload cannot be called on the default domain (remember, it must live as long as the process) or on an application domain that has already been unloaded. In these cases, the CLR throws a CannotUnloadAppDomainException . Receiving Application Domain Unload Events
The CLR raises an event to notify the hosting application that an application domain is being unloaded. This event can be received in either managed or unmanaged code. In managed code, domain unload notifications are raised through the
System.AppDomain.DomainUnload
event. In unmanaged code, the CLR sends application domain unload notifications to
The AppDomain.DomainUnload event takes the standard event delegate that includes arguments for the object that originated the event (the sender ) and any additional data that is specific to that event (the EventArgs ). The instance of System.AppDomain representing the application domain that is being unloaded is passed as the sender parameter. The EventArgs are null. The following code snippet shows a simple event handler that prints the name of the application domain that is being unloaded:
public static void DomainUnloadHandler(Object sender, EventArgs e)
{
AppDomain ad = (AppDomain)sender;
Console.WriteLine("Domain Unloaded Event fired: " + ad.FriendlyName);
}
This event is hooked up to the domain using the standard event syntax:
AppDomain ad1 = AppDomain.CreateDomain("Application Domain 1");
ad1.DomainUnload += new EventHandler(DomainUnloadHandler);
Because information about the unloaded domain is passed as a parameter, it's
Receiving Domain Unload Events Using the IActionOnCLREvent InterfaceListening to domain unload events in managed code is much easier to program and therefore is the approach you're likely to use most. However, you can also receive these events in unmanaged code by providing an object that implements the IActionOnCLREvent interface. IActionOnCLREvent contains one method ( OnEvent ) that the CLR calls to send an event to a CLR host. Here's the definition of IActionOnCLREvent from mscoree.idl:
interface IActionOnCLREvent: IUnknown
{
HRESULT OnEvent(
[in] EClrEvent event,
[in] PVOID data
);
}
The CLR
You register your intent to receive events by passing your implementation of IActionOnCLREvent to the CLR through the ICLROnEventManager interface. As with all CLR-implemented hosting managers, you obtain this interface from ICLRControl as shown in the following code snippet:
// Get an ICLRRuntimeHost by calling CorBindToRuntimeEx.
ICLRRuntimeHost *pCLR = NULL;
hr = CorBindToRuntimeEx(......,(PVOID*) &pCLR);
// Get the CLR Control object.
ICLRControl *pCLRControl = NULL;
pCLR->GetCLRControl(&pCLRControl);
// Ask for the Event Manager.
ICLROnEventManager *pEventManager = NULL;
pCLRControl->GetCLRManager(IID_ICLROnEventManager,
(void **)&pEventManager);
ICLROnEventManager
contains two
interface ICLROnEventManager: IUnknown
{
HRESULT RegisterActionOnEvent(
[in] EClrEvent event,
[in] IActionOnCLREvent *pAction
);
HRESULT UnregisterActionOnEvent(
[in] EClrEvent event,
[in] IActionOnCLREvent *pAction
);
}
Notice that both methods take a parameter of type EClrEvent . This enables you to register to receive only those events you are interested in.
To
Listing 5-2. BoatRaceHost.cpp
#include "stdafx.h"
#include "CHostControl.h"
// This class implements IActionOnCLREvent. An instance of this class
// is passed as a "callback" to the CLR's ICLROnEventManager to receive
// a notification when an application domain is unloaded.
class CActionOnCLREvent : public IActionOnCLREvent
{
public:
// IActionOnCLREvent
HRESULT __stdcall OnEvent(EClrEvent event, PVOID data);
// IUnknown
virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
// constructor and destructor
CActionOnCLREvent()
{
m_cRef=0;
}
virtual ~CActionOnCLREvent()
{
}
private:
long m_cRef; // member variable for ref counting
};
// IActionOnCLREvent methods
HRESULT __stdcall CActionOnCLREvent::OnEvent(EClrEvent event, PVOID data)
{
wprintf(L"AppDomain %d Unloaded\n", (int) data);
return S_OK;
}
// IUnknown methods
HRESULT __stdcall CActionOnCLREvent::QueryInterface(const IID &iid,void **ppv)
{
if (!ppv) return E_POINTER;
*ppv=this;
AddRef();
return S_OK;
}
ULONG __stdcall CActionOnCLREvent::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG __stdcall CActionOnCLREvent::Release()
{
if(InterlockedDecrement(&m_cRef) == 0){
delete this;
return 0;
}
return m_cRef;
}
int main(int argc, wchar_t* argv[])
{
// Start the CLR. Make sure .NET Framework version 2.0 is used.
ICLRRuntimeHost *pCLR = NULL;
HRESULT hr = CorBindToRuntimeEx(
L"v2.0.41013,
L"wks",
STARTUP_CONCURRENT_GC,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*) &pCLR);
assert(SUCCEEDED(hr));
// Create an instance of our host control object and register
// it with the CLR.
CHostControl *pHostControl = new CHostControl();
pCLR->SetHostControl((IHostControl *)pHostControl);
// Get the CLRControl object. This object enables us to get the
// CLR's OnEventManager interface and hook up an instance of
// CActionOnCLREvent.
ICLRControl *pCLRControl = NULL;
hr = pCLR->GetCLRControl(&pCLRControl);
assert(SUCCEEDED(hr));
ICLROnEventManager *pEventManager = NULL;
hr = pCLRControl->GetCLRManager(IID_ICLROnEventManager,
(void **)&pEventManager);
assert(SUCCEEDED(hr));
// Create a new object that implements IActionOnCLREvent and
// register it with the CLR. We're only registering to receive
// notifications on app domain unload.
CActionOnCLREvent *pEventHandler = new CActionOnCLREvent();
hr = pEventManager->RegisterActionOnEvent(Event_DomainUnload,
(IActionOnCLREvent *)pEventHandler);
assert(SUCCEEDED(hr));
hr = pCLR->Start();
assert(SUCCEEDED(hr));
// Get a pointer to our AppDomainManager running in the default domain.
IBoatRaceDomainManager *pDomainManagerForDefaultDomain =
pHostControl->GetDomainManagerForDefaultDomain();
assert(pDomainManagerForDefaultDomain);
// Enter a new boat in the race. This creates a new application domain
// whose id is returned.
int domainID = pDomainManagerForDefaultDomain->EnterBoat(L"Boats",
L"J29.ParthianShot");
// Unload the domain the boat was just created in. This will
// cause the CLR to call the OnEvent method in our implementation of
// IActionOnCLREvent.
pCLR->UnloadAppDomain(domainID);
// Clean up.
pDomainManagerForDefaultDomain->Release();
pHostControl->Release();
return 0;
}
In this sample, the implementation of IActionOnCLREvent is provided by the CActionOnCLREvent class. Notice the implementation of the OnEvent method is very simpleit just prints out the identifier of the application domain that is being unloaded. In the main program an instance of CActionOnCLREvent is created and registered with the CLR by calling the RegisterActionOnEvent method on the ICLROnEventManager pointer obtained from ICLRControl . To trigger the event handler to be called, an application domain is unloaded using ICLRRuntimeHost::UnloadAppDomain . |