Die AppDomain Das unbekannte Wesen? 19.11.2013 Dr. Jochen Manns EMail: dev-1@psimarron.net Website: http://www.jochen.jochen-manns.de
Marshalling in der Praxis Agenda Kleine Einführung AppDomains und die Anwendung Varianten von Marshalling Marshalling in der Praxis By Value By Reference It depends…
Übersicht
Jeder Code läuft in einer AppDomain .NET und die AppDomain Jeder Code läuft in einer AppDomain Threads sind ein orthogonales Konzept Mindestens eine AppDomain pro Prozess Default AppDomain wird beim Starten angelegt Weitere müssen explizit erzeugt werden Im Normallfall unsichtbar Nur die Default AppDomain Keine Notwendigkeit, AppDomains zu kennen
Warum benutzt man AppDomains? Eine Art Prozess im Windows Prozess In .NET hochgradig isoliert Nativer Code kennt keine Grenzen Eigene Sicherheitsregel Vor allem Ausführungspfade für Bibliotheken Erweiterungskonzepte / PlugIns Sichere, abgeschirmte Umgebung der PlugIns Kontrollierte Schnittstelle zum Host Zeitweises Laden von Bibliotheken
Ein Prozess pro Application Pool ASP.NET und IIS Ein Prozess pro Application Pool Eine AppDomain pro virtuellem Verzeichnis Mehrere virtuelle Verzeichnisse pro Pool Trotzdem vollständig isoliert (.NET) ASP.NET Hosting ApplicationHost Klasse Volle ASP.NET Laufzeitumgebung Web.Config, WebApi, MVC, ASPX, ASMX, … Separate AppDomain für den Host
Kommunikation
Kommunikation und Marshalling Isolation kontrolliert durchbrechen Proxy / Stub Objekte Proxy in der steuernden AppDomain (Host) Stub in der untergeordnete AppDomain Inter-AppDomain Methodenaufrufe AppDomain Wechsel auf einem einzigen Thread Indirekte Aufrufe kosten Zeit Was ist aber mit Daten / Objekten? Marshalling in zwei Varianten
Vollständige Übertragung Marshal By Value Vollständige Übertragung Keine Kommunikation über die Daten Hohe Unabhängigkeit der AppDomains Datenaustausch, evtl. umfangreich [Serializable] Binäre Variante Berücksichtigt alle .NET Fields [NonSerialized] Zwingend für alle referenzierten Objekte
Proxy Referenz auf einen Stub Marshal By Reference Proxy Referenz auf einen Stub Keine Datenübertragung Zugriff erfordern erneuten AppDomain Wechsel Jede Codeausführung (auch der Abruf eines .NET Fields) erfolgt in der AppDomain, in der das Objekt erzeugt wurde Im By Value Fall wird Code immer in der AppDomain des Aufrufers ausgeführt Ableiten von MarshalByRefObject
Böse Falle Garbage Collection Ein Proxy ist keine starke Referenz Der Stub muss sich vor Freigabe schützen InitializeLifetimeService Ansonsten ist der Proxy irgendwann kaputt
ASP.NET Hosting
WebApi Hosting in einem Dienst Datenverwaltung im Dienst Datenbank Steuerungslogik Kontrollierte Laufzeitumgebung Remote Schnittstelle für Clients WCF WebApi Self Hosting ASP.NET Hosting Für REST, aber auch ASPX, ASMX, … Betriebssicherheit durch Isolation
Zugriff auf die Dienst AppDomain ASP.HET Hosting über Proxy / Stub Dienst kann auf ASP.NET Laufzeit zugreifen Sequenz immer ähnlich m_Runtime = (IASPNETProxy) ApplicationHost.CreateApplicationHost( typeof( ASPNETProxy), … ); m_Runtime.SetServer( m_Server ); Dienstobjekt in ASP.NET verfügbar machen By Reference Stub lebt im Dienst Proxy kann von ASP.NET angesprochen werden
Marshalling
REST via Marshal By Reference var response = new ClientResponse { Data = proxy.Analyse( 12 ) }; public interface IServiceProxy { string Analyse( int depth ); } [DataContract] public class ClientResponse { [DataMember] public string Data { get; set; } } Die Antwort an den Client wird in der ASP.NET AppDomain erstellt. Jeder Aufruf an den Dienstproxy muss die AppDomain Grenze überschreiten. Alle Objekte müssen eine Überschreitung zulassen: By Value, wie alle einfachen .NET Klassen By Reference, üblicherweise eigene Klassen Wilder Zugriff führt zu Fehlermeldungen, gute Kontrolle! Die Ausführungsgeschwindigkeit kann leiden Besondere Vorsicht bei Listen von Objekten Viele Einzelzugriffe auf .NET Fields & Properties evtl. problematisch
REST via Marshal By Value var serviceResponse = proxy. Analyse( 12 ); var response = new ClientResponse { Data = serviceResponse.Data }; public interface IServiceProxy { ServiceResponse Analyse( int depth ); } [Serializable] public class ServiceResponse { public string Data { get; set; } } Die AppDomain des Dienstes bereitet alles vor Die Bearbeitung findet vollständig dort statt In der ASP.NET AppDomain wird noch einmal umkopiert Die Dienst AppDomain kann natürlich auch direkt das Ergebnis erzeugen Dann muss dieses aber auch By Value sein var response = proxy.Analyse( 12 ); public interface IServiceProxy { ClientResponse Analyse( int depth ); } [DataContract, Serializable] public class ClientResponse { [DataMember] public string Data { get; set; } }
Code Abhängigkeiten using System; using System.Collections.Generic; using Sample.ClientNamespace; namespace Sample.ServiceNamespace { public class ServiceItem public Service Service { get; private set; } public string Data { get; private set; } public ClientItem ToClient() return new ClientItem { Data = Data }; } public class Service : MarshalByRefObject private readonly List<ServiceItem> m_items = new List<ServiceItem>(); public ClientItem ToClient( int index ) return m_items[index].ToClient(); using System; using System.Runtime.Serialization; using Sample.ServiceNamespace; namespace Sample.ClientNamespace { [DataContract, Serializable] public class ClientItem public string Data { get; set; } } public class TheController public ClientItem GetItem( int index ) return ASPNETRuntime.ServiceProxy.ToClient( index ); Man beachte: ServiceItem ist WEDER By Value NOCH By Reference und ist daher NUR im Dienst verfügbar
Entkoppeln Die statische Methode wird in der AppDomain des using System; using System.Runtime.Serialization; using Sample.ServiceNamespace; namespace Sample.ClientNamespace { public class TheController public ClientItem GetItem( int index ) return ASPNETRuntime.ServiceProxy.ToClient( index, ClientItem.FromService ); } [DataContract, Serializable] public class ClientItem public string Data { get; set; } public static ClientItem FromService( ServiceItem item ) return new ClientItem { Data = item.Data }; Die statische Methode wird in der AppDomain des Dienstes ausgeführt! using System; using System.Collections.Generic; namespace Sample.ServiceNamespace { public class ServiceItem public Service Service { get; private set; } public string Data { get; private set; } } public class Service : MarshalByRefObject private readonly List<ServiceItem> m_items = new List<ServiceItem>(); public TClientType ToClient<TClientType>( int index, Func<ServiceItem, TClientType> factory ) return factory( m_items[index] );
Marshal By Value nach Bedarf Mehrere By Value Varianten nutzen Je nach Kontext verschiedene Inhalte Pro REST Controller oder gar pro Methode Gezielt mischen By Reference für äußere Objekte By Value für Detailobjekte Querreferenzen können verloren gehen Querreferenzen lassen Datenmenge explodieren
Zusammenfassung AppDomains sind isolierte Bereiche Innerhalb eines Windows Prozesses Methoden werden über Proxy Klassen aufgerufen Daten werden By Value oder By Reference ausgetauscht Beide Varianten haben Vor- und Nachteile Hybride führen oft zur besten Lösung Generische Methoden zur Entkoppelung