Kleine Falle ASP.NET Hosting: HttpListener und Threads

Die einfachste Methode, ASP.NET zu hosten ist es, den HttpListener als HTTP Protokollschicht einzusetzen (wenn man nicht gerade eine eigene Implementierung schreiben will). Allerdings gibt es hier einen kleinen Haken: der HttpListener scheint an den Thread gebunden zu sein, der ihn gestartet hat (Start oder BeginGetContext).

Startet man den Listener auf dem Haupthread der Anwendung, funktioniert alles wie gewünscht und der Listener erkennt automatisch das Ende der Anwendung. So weit so gut. Wenn man allerdings einen Thread verwendet, der explizit oder implizit beendet wird, so terminiert auch der Listener. In meinem Fall knallte es beim expliziten Beenden: ich hatte einen Thread, der einfach nur den Listener konfigurierte und startet und sich dann beendet – damit die Initialisierung der Anwendung (in diesem Fall der VCR.NET Windows Dienst) im Hauptstrang so schnell wie möglich durchläuft.

Mit ThreadPool.QueueUserWorkItem geht es dann, aber mit einer im Hintergrund drohenden Gefahr: der ThreadPool beendet die Aufgabe und verwendet den Thread dann später für andere Aufgaben weiter. Der Listener bleibt an diesen Thread gebunden, der überhaupt nichts mehr mit ihm zu tun hat. Und wenn der ThreadPool irgendwann mal einen Thread wegwirft? Den Fehler findet man vermutlich nie!

Ich habe mich daher entschieden, mich nicht auf die Willkür zu verlassen und halte den Thread, der den Listener hochfährt, bis zum Ende der Anwendung oben (über ein Manual/AutoResetEvent). Nur so habe ich ein gutes Gefühl, dass nicht doch plötzlich die Kommunikation des Clients zum ASP.NET zusammenbricht.

Zumindest sollte man das im Hinterkopf haben!

Jochen

Serverarchitektur bei ASP.NET Hosting

Vielleicht einfach ein paar kurze Gedanken zu den letzten Arbeiten an VCR.NET 3.9, etwas abstrahiert. Im Endeffekt geht es darum, eine Serverfunktionalität mit präziser Lebenszeitkontrolle mit einer Web Oberfläche anzubieten. Die Kontrolle der Lebenszeit läßt sich ideal durch das Hosten des Servers in einem Windows Dienst erreichen, für die Web Oberfläche wäre natürlich der Microsoft Internet Information Server die bevorzugte Lösung. Mit dem ASP.NET Hosting läßt sich beides weitgehend kombinieren (es ist allerdings kaum möglich, alle Autorisierungsoptionen des IIS nachzubilden, aber das braucht man auch nicht immer).

Zu beachten ist allerdings, dass ein virtuelles Verzeichnis immer in einer eigenen .NET AppDomain läuft. Unser Ansatz besteht daher immer aus mindestens zwei AppDomains, was einige besondere Aspekte mit sich bringt. Ich stelle hier in Stichworten einmal die beim VCR.NET implementierte Lösung vor.

1. Der .NET Dienst wird natürlich in einer ersten AppDomain gestartet. Hier soll auch der Server leben.
2. Der Server wird als .NET Instanz einer MarshalByRefObject Klasse (Server) erzeugt.
3. Für das ASP.NET Hosting wird eine weitere MarshalByRefObject Klasse (Host) benötigt, die in dem virtuellen Verzeichnis erstellt wird.
4. Nach dem Starten des virtuellen Verzeichnisses erhält Host die Server Referenz aus der ersten AppDomain.
5. Alle Zugriffe aus dem virtuellen Verzeichnis erfolgen über dese Referenz und werden in der ersten AppDomain ausgeführt.
6. Damit die beiden (Server und Host) Inter-AppDomain Referenzen nicht durch die Remoting Lebenszeitkontrolle spontan gelöst werden, melden deren InitializeLifetimeService Methoden null.
7. Alle Klassen, die zwischen den AppDomains ausgetauscht werden sollen, müssen MarshalByRefObject oder [Serializable] sein. VCR.NET setzt auf serialisierbare Klassen, so dass zwar für die Serialisierung und Deserialisierung bezahlt werden muss, die Objekte dann aber ohne Kommunikationsverluste weiter verwendet werden können. Man beachte, dass hier die binäre Serialisierung zum Einsatz kommt – man siehe dazu einen früheren Eintrag in diesem BLog.

Zur Verwaltung der Konfiguration wird ein einziges Konfigurationsobjekt eingesetzt, über das alle Zugriff (auch schreibend) erfolgen. Dieses wird in der ersten AppDomain angelegt. Damit es nicht in alle Klassen durchgereicht werden muss (hier gibt es etwa Methoden wie Log(string message)), erhählt die Konfigurationsklasse (Config) ein statisches Feld Current, dass bei der Intialisierung des Windows Dienstes auf der ersten AppDomain belegt wird (Config.Current = new Config()). Zusätzlich bietet die Server Klasse eine Eigenschaft zum Zugriff auf Config.Current an. In Schritt 4. oben wird in der AppDomain des virtuellen Verzeichnisses eine Referenz des eindeutigen Konfigurationsobjektes in das statische Feld als Referenz übernommen (Config.Current = Server.CurrentConfiguration) – ein anderes statisches Feld, da statische Felder nur pro AppDomain eindeutig sind. So arbeiten beide AppDomains immer mit der selben Konfiguration – Config ist natürlich ein MarshalByRefObject, dessen InitializeLifetimeService null liefert.

Nun, für einige ist das vermutlich trivial, aber vielleicht hilft die Information hier dem einen oder anderen, einige Fallstricke zu vermeiden und etwas Zeit zu sparen.

Viel Glück

Jochen

VCR.NET 3.9 Verluste: SOAP Web Service

Schon von Anbeginn an bot der Windows Dienst VCR.NET Recording Service eine SOAP Schnittstelle an, die von Clients zur Steuerung verwendet werden konnten. Tatsächlich gibt es in 3.5 zwei verschiedene Schnittstellen, eine für den regulären Betrieb wie die Aufzeichnungsplanung und einen für den so genannten Zapping oder LIVE Modus. Beide Schnittstellen werden zudem in zwei Varianten angeboten, eine für die Versionen vom VCR.NET, die nur ein Geräteprofil verwenden konnten und eine, bei der das Geräteprofil bei jeder Operation über seinen Namen ausgewählt werden kann.

Zukünftige VCR.NET Versionen (die nach der 3.9, so es sie denn geben sollte) werden möglicherweise veränderte SOAP Schnittstellen auf Basis von WCF (Windows Communication Framework) anbieten. Für die 3.9 wird es keine neuen Varianten der beiden primären Web Services geben, vielmehr wird VCR.NET 3.9 versuchen, auf Basis der neuen Infrastruktur die alten Schnittstellen unverändert anzubieten. Das geht soweit auch schon, hat aber einige Nachteile – die vermutlich aber kaum jemanden betreffen.

Zum einen können neue Funktionalitäten nicht genutzt werden, wenn diese über die Protokollstrukturen der alten Schnittstellen nicht verfügbar sind. Das ist eher unkritisch, hier ging es etwa um die präzisere Auswahl der Quellen (früher Sender genannt). Kritischer ist aber, dass VCR.NET einige Informationen intern nicht mehr pflegt und auf Anfrage daher auch nicht mehr zur Verfügung stellen kann. Hier ist vor allem die Senderbeschreibung zu erwähnen: DVB.NET stellt ab der Version 3.9 (auf der VCR.NET 3.9 basiert) zum Beispiel die Daten zu den Tonspuren nicht mehr zur Verfügung. Bei Anfrage an VCR.NET 3.9 über die alten Schnittstellen werden hier leere Listen geliefert. Tatsächlich benötigt der DVB.NET / VCR.NET Viewer als einer der VCR.NET SOAP Clients diese Information überhaupt nicht, anderen Clients wird es ähnlich gehen. Genauso ist es nicht exakt möglich, die Auswahl der Teildatenströme auf die vier Wahrheitswerte (Alle Sprachen, Dolby Digital, DVB Untertitel, Videotext) abzubilden, da die interne Verwaltung nun sehr viel feinere Variationen zulässt. Allerdings versucht VCR.NET sein Bestes, ein möglichst exaktes Abbild der tatsächlichen Konfiguration zu übermitteln.

In Einzelfällen ist zu klären, an welchen Stellen die modifizierten Informationen ein Problem darstellen. Im Wesentlichen bleibt aber die folgende Aussage: VCR.NET 3.9 wird voraussichtlich keine neuen SOAP Dienste oder Varianten der alten anbieten. Die vorhandenen Clients wie der Viewer oder das Kontrollzentrum werden die alten SOAP Schnittstellen benutzen – vielleicht wird es das eine oder andere Hintertürchen geben, um eine versionsunabhängige Version des Viewers zu erstellen – das Kontrollzentrum ist da eher harmlos.

Bis bald

Jochen