I tidligere indlæg så vi på motivationen for at bruge AppDomains i en applikation. I dette indlæg bliver det lidt mere konkret, da vi skal se på de nødvendige konstruktioner i forbindelse med brug af AppDomains.
AppDomains er repræsenteret ved klassen AppDomain, og oprettelse sker via den statiske metode CreateDomain(). Derefter skal vi have et eller flere assemblies indlæst, hvilket kan gøres på forskellige måder afhængig af, hvordan vi ønsker at afvikle kode. Når vi er færdige, kan vi nedlægge vores AppDomain og dermed frigive de indlæste assemblies. Sidstnævnte gøres via den statiske metode Unload() på AppDomain.
Hvis vi lægger ud med den simpleste situation, hvor vi blot ønsker at lave et AppDomain, indlæse en applikation i dette og efterfølgende nedlægge AppDomainet, ser koden ud som følger:
AppDomain domain = AppDomain.CreateDomain("New AppDomain");
domain.ExecuteAssembly("application.exe");
AppDomain.Unload(domain);
Det er simpelt, men i realiteten heller ikke så interessant. I dette tilfælde opretter vi et ny AppDomain og indlæser en applikation, der derefter afvikles. Det vil sige, at de assemblies, application.exe afhænger af, indlæses i lokalt i domain. I og med at der er tale om en applikation, vil afviklingen af application.exe betyde, at Main() bliver kaldt ganske som forventet. Når denne er kørt til ende, returnerer kontrollen til vores applikation, hvor vi nedlægger domain og slipper dermed af med de assemblies, der er blevet indlæst i dette.
Ovenstående fungerer kun med applikationer, da de har et veldefineret entry point. Ønsker vi at kalde en vilkårlig statisk metode i et assembly, skal vi bruge DoCallBack()-metoden som vist her:
static void Main(string[] args) {
AppDomain domain = AppDomain.CreateDomain("New AppDomain");
domain.DoCallBack(Hello);
AppDomain.Unload(domain);
}
static void Hello() {
Console.WriteLine("Location: {0}", AppDomain.CurrentDomain.FriendlyName);
}
Læg mærke til, at vi via AppDomain.CurrentDomain kan aflæse det konkrete AppDomain. I eksemplet indlæser vi en kopi af den kaldende applikation, for derefter at kalde metoden Hello(). Det er selvfølgelig et noget konstrueret eksempel, der kun tjener til at illustrere syntaksen.
Afviklingen af koden i domain sker i begge tilfælde synkront. Vores applikation kan altså ikke lave andet, mens application.exe afvikler, og derfor er disse fremgangsmåder kun interessante i et begrænset antal tilfælde.
Som nævnt er der nemlig ikke sammenhæng mellem AppDomains og tråde. Vores kode afvikles i praksis ved, at den kaldende applikations tråd simpelthen hopper fra det ene AppDomain til det andet og tilbage igen.
Hvis vi vil have kode i vores AppDomains til at afvikle selvstændigt, skal vi således have flere tråde i spil, hvilket kan gøres på flere måder. Vi kan anvende tråde i CLRens thread pool, hvilket er en god ide til små opgaver. Til længere opgaver vil det nok være en bedre ide at oprette selvstændige tråde efter behov. I begge tilfælde kan vi lade det være op til den kaldende applikation eller til de enkelte delapplikationer at oprette/bruge de nødvendige tråde. Hvis vi overlader det til delapplikationerne, vil det være en god ide, at oprette en abstrakt baseklasse, der indkapsler genereringen af tråde. Det kunne f.eks. se ud som følger:

Vores interface specificerer to metoder: Start() og Stop(). Start() laver en tråd og sætter denne til at afvikle RunBusinessLogic(). Stop() signalerer til applikationen at den skal lukke ned. RunBusinessLogic() kalder den virtuelle metode BusinessLogic() og indkapsler håndtering af exceptions. Konkrete applikationer skal derfor blot arve fra Application og overstyre implementeringen af BusinessLogic().
En anden begrænsning ved den simple fremgangsmåde er, at vi ikke har nogen måde at påvirke afviklingen af koden i domain. En af ideerne er jo netop, at kode i et AppDomain ikke skal kunne påvirke kode i et andet, men hvis vi vil lave en værtsapplikation, der indlæser og afvikler forskellige delapplikationer dynamisk, har vi brug for at kunne kommunikere med de enkelte applikationer.
Kommunikation mellem forskellige AppDomains sker via remoting. Ved remoting marshalles typer over AppDomain-grænser via enten marshal by value eller marshal by reference. Førstnævnte kræver at typen er serializable og er kun interessant i et begrænset antal tilfælde, da dette medfører, at der oprettes en lokal kopi af den aktuelle type. Ved marshal by reference skal typen arve fra MarshalByRefObject. Herefter håndteres al marshalling automatisk. Det kaldende AppDomain får en reference til en konkret instans af den faktiske type i det andet domæne. Via denne kan vores værtsapplikation påvirke tilstanden af en instans i et andet domæne.
Ønsker vi at oprette en reference til en instans i et andet AppDomain, skal vi kalde CreateInstanceAndUnwrap() på det ønskede AppDomain som vist her:
AppDomain domain = AppDomain.CreateDomain("New AppDomain");
IApplication application = (IApplication)domain.CreateInstanceAndUnwrap("Applications", "Applications.WorkingApplication");
application.EntryPoint();
Læg mærke til, at vi anvender en interface-reference samt indlæser den ønskede type via en tekststreng og ikke via typens egen beskrivelse. Derved undgår vi, at værtsdomænet også indlæser de berørte assemblies. Det kræver naturligvis, at IApplication og de konkrete applikationer er erklæret i forskellige assemblies. Bemærk også, at det kan være nødvendig, at hjælpe AppDomain lidt på vej i forhold til placering af de relevante DLLer. Hvis der er behov for det, kan vi oprette en instans af AppDomainSetup, sætte de relevante stier og oprette vores AppDomain med den opsætning. Det har jeg dog udeladt i eksemplet af hensyn til overskueligheden.
I dette indlæg har vi set på, hvordan AppDomains kan oprettes samt hvordan vi kan kommunikere med dem. I næste indlæg skal vi se lidt på nogle af de tekniske detaljer i forhold til AppDomains.