21. Dezember 2011

Korrektes Event Handling (registrieren und deregistrieren) in dotnet

Auf der Suche nach einer Möglichkeit um zu überprüfen ob ein Object einen EventHandler registriert hat,
bin ich auf folgendern überaus gut erklärten Post von herbivore auf myCSharp.de gestoßen, den ich im Folgenden einfach übernehmen werde:


Machmal sagt Code mehr als tausend Worte. So definiert man einen eigenen Event
(folgt nicht den Microsoft-Empfehlungen, dazu später mehr):

C#-Code:
using System;

public delegate void MyHandler (string str_nick, string str_channel);

public class Test
{
   public event MyHandler MyEvent;

   protected virtual void OnMyEvent (string str_nick, string str_channel)
   {
      MyHandler myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (str_nick, str_channel);
      }
   }

   public void DoSomething ()
   {
      // ...
      OnMyEvent ("herbivore", "mycsharp");
      // ...
   }

}

abstract class App
{
   public static void Main (string [] astrArg)
   {
      Test test = new Test ();

      test.MyEvent += new MyHandler (ThisMyEvent);
      test.DoSomething ();
   }

   public static void ThisMyEvent (string str_nick, string str_channel)
   {
      Console.WriteLine ("ThisMyEvent (" + str_nick + ", "
                                         + str_channel + ")");
   }
}


PS: Dieser Code funktioniert zwar, folgt aber nicht der Empfehlung von MS, an die man sich bei Events wirklich besser halten sollte. Wie ein eigener Event nach der Empfehlung von MS aussehen sollte, steht im folgenden Beitrag.



Und so erstellt man einen eigenen Event von Typ EventHandler (wie es von MS empfohlen wird):

C#-Code:
using System;

public class Test
{
   // --- schnipp ---

   public event EventHandler MyEvent;

   protected virtual void OnMyEvent (EventArgs e)
   {
      EventHandler myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (this, e);
      }
   }

   // --- schnapp ---

   public void DoSomething ()
   {
      // ...
      OnMyEvent (EventArgs.Empty);
      // ...
   }
}

static class App
{
   public static void Main (string [] astrArg)
   {
      Test myObject = new Test ();
      myObject.MyEvent += myObject_MyEvent;

      myObject.DoSomething ();
   }

   public static void myObject_MyEvent (Object objSender, EventArgs e)
   {
      Console.WriteLine ("myObject_MyEvent (" + objSender + ", " + e + ")");
   }
}

Wobei man natürlich auch eine eigene MyEventEventArgs-Klasse erstellen und verwenden könnte. Siehe dazu das Beispiel von Programmierhans weiter unten.

Hier noch ein Blick auf die wichtigen Bestandteile des Beispiels:


1. Den Event selbst:

C#-Code:
public event EventHandler MyEvent;

Ist in der Klasse definiert, die den Event zur Verfügung stellen will und im Wesentlichen eine Variable, in der die jeweils registrierten EventHandler gespeichert sind. Ist null, wenn kein EventHandler registriert ist. Namen von Events sollten nie mit On beginnen. "MyEvent" ist nur ein Platzhalter, den ihr durch euren Namen für den Event ersetzen solltet, z.B. TextChanged o.ä.


2. Die event-auslösende Methode:

C#-Code:
protected virtual void OnMyEvent (EventArgs a)

Deren Namen sollte immer mit On beginnen und danach sollte der Name des Events folgen. Ist natürlich in der gleichen Klasse definiert. Sie wird von dieser Klasse aufgerufen, um den Event zu feuern. Im Beispiel also in DoSomething in der Zeile OnMyEvent (EventArgs.Empty);

Die Implementierung der event-auslösenden Methode kann man durch die Raise-Erweiterungsmethode, die weiter  unten vorgestellt wird, vereinfachen.


3. Den/die EventHandler:

C#-Code:
public static void myObject_MyEvent (Object objSender, EventArgs e)

Der EventHandler ist in der Klasse definiert, die über das Event informiert werden will. Das ist typischerweise eine andere Klasse, als die, die das Event definiert. Namen von EventHandlern sollten nie mit On beginnen. VS benennt EventHandler nach dem auch hier verwendeten Muster: Variablenname Unterstrich EventName.


4a. Das Registrieren des EventHandler für den Event (auch Abonnieren des Events genannt):

EventHandler werden registriert mit +=

C#-Code:
myObject.MyEvent += myObject_MyEvent;

Unter .NET 1.x musste man noch schreiben:

C#-Code:
myObject.MyEvent += new EventHandler (myObject_MyEvent);

Ab .NET 2.0 ist die kürzere Schreibweise möglich, die man auch bevorzugen sollte.


4b. Das Deregistrieren des EventHandler für den Event (auch Entfernen oder Abhängen des EventHandlers genannt):

Zum Deregistrieren eines EventHandlers verwendet man -= statt +=.

Es ist nicht erforderlich, beim Deregistrieren exakt dieselbe Instanz des Delegaten zu verwenden, sondern es reicht, wenn der Delegat auf dieselbe Methode verweist.

C#-Code:
myObject.MyEvent += new EventHandler (myObject_MyEvent);
myObject.MyEvent -= new EventHandler (myObject_MyEvent);

Durch die zweite Zeile wird der EventHandler myObject_MyEvent wieder entfernt, obwohl an sich zwei separate Delegaten-Objekte verwendet werden (new), die aber eben beide auf dieselbe Methode verweisen. Schon deshalb ist die C#-2.0-Schreibweise ohne new vorzuziehen.

Das rechtzeitige Deregistrieren von nicht mehr benötigten Events wird oft vergessen. Das kann allerdings negative Konsequenzen haben. Hat ein Objekt B einen Event von Objekt A registriert und wird das Objekt B nicht mehr benötigt, kann die Speicherfreigabe des Objekts B verzögert oder verhindert werden (memory leak). Der Grund ist, dass das Objekt A noch eine Referenz auf das Objekt B hält. Diese Referenz "versteckt" sich im Delegaten für den EventHandler (Delegate.Target). Deshalb sollte man rechtzeitig alle EventHandler die B registriert hat entfernen.

Noch schlimmer ist es, wenn das Objekt B zerstört wird (Dispose) und dabei die EventHandler nicht entfernt werden. Denn wird ein solches Event ausgelöst, wird der weiterhin registrierte EventHandler aufgerufen und dieser greift dann im schlimmsten Fall auf das bereits zerstörte Objekt zu.

+= und -= sind übrigens die einzigen beiden Operatoren, mit denen man auf Events einer Klasse von außen zugreifen kann. Man kann insbesondere nicht abfragen, ob ein EventHandler bereits registriert ist oder nicht. Das ist aber normalerweise gar nicht nötig. Möchte man einen EventHandler entfernen, obwohl man nicht weiß, ob er momentan registriert ist oder nicht, kann man trotzdem -= benutzen. Im Ergebnis ist der EventHandler deregistriert, egal ob er vorher registriert war oder nicht. Möchte man einen EventHandler hinzufügen, aber nur, wenn er nicht bereits registriert ist, benutzt man erst -= und dann +=. Im Ergebnis ist der EventHandler genau einmal registriert, egal ob er vorher registriert war oder nicht. Das gilt natürlich nur, wenn man konsequent ist und es an keiner Stelle zulässt, dass ein EventHandler mehr als einmal registriert wird.


Überlegungen zur Thread-Sicherheit

In vielen Büchern wird in der event-auslösende Methode direkt das Event auf null abgefragt, bevor es gefeuert wird, also so:

C#-Code:
      if (MyEvent != null) {
         MyEvent (this, e);
      }

Auch hier stand bisher der Code so. Dass diese Vorgehensweise nicht thread-sicher ist (zwischen Abfrage und Feuern könnte ein anderer Thread den letzten EventHandler deregistrieren und dann würde das Feuern eine NullReferenceException auslösen), wurde in Kauf genommen, weil viele Klassen sowieso nicht thread-sicher ausgelegt sind und Synchronisierung (z.B. durch lock) nur unnötig Zeit kostet, wenn die Klasse ohnehin nur single-threaded eingesetzt wird.

Es gibt aber eine so preiswerte Lösung, um Thread-Sicherheit zu erreichen, dass man diese grundsätzlich immer einsetzen kann und sollte. Man muss nur dafür sorgen, dass man bei Abfrage und Feuern eine Referenz auf dasselbe Objekt verwendet. Denn Delegaten - und mithin Events - sind immutable. Das Registrieren oder Deregistrieren eines EventHandlers erzeugt immer ein neues Objekt mit den Änderungen. Diesen Umstand kann man nun ausnutzen, denn MyEvent ruft bei jedem Zugriff das jeweils aktuelle Objekt und damit an beiden Stellen ggf. unterschiedliche Objekte ab, wogegen myEvent an beiden Stellen garantiert dasselbe (und da immutable auch unveränderte) Objekt liefert.

C#-Code:
      EventHandler myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (this, e);
      }

Diese Lösung kostet nur eine zusätzlich Referenzzuweisung und spart dabei sogar noch einen Property-Zugriff.

Es sei jedoch darauf hingewiesen, dass trotz oder gerade wegen dieses Vorgehen ein EventHandler auch noch aufgerufen werden kann, *nachdem* er schon aus dem Event ausgetragen (deregistriert) wurde, wodurch sich die Notwendigkeit einer zusätzlichen Synchronisierung ergeben kann.

Dank geht an Golo Roden, der mich auf diese Lösung hingewiesen hat.


Vereinfachung der event-auslösenden Methode bei gleicher Thread-Sicherheit

Mit der Raise-Erweiterungsmethode, die weiter  unten vorgestellt wird, lässt sich die Implementierung der event-auslösenden Methode bei gleicher Thread-Sicherheit noch einfacher realisieren.

herbivore



Danke herbivore :)

Tomcat als Service unter Windows installieren UND für den Jazz Team Server konfigurieren

Weil ich es eben gemacht habe,
und falls ich es in Zukunft wieder brauche, mir ein wenig gegoogle erpare,
folgend eine kurze Zusammenfassung, wie man das Ganze zustande bringt:

Installation von Tomcat als Service / Dienst:

Nach dem download (hier) wird einfach der Installer wizard wie gewohnt ausgeführt.

Anschließend geht es darum, tomcat als Dienst festzulegen und automatisch zu starten.

Sehr geholfen hierbei hat mir das offizielle Windows service HOW-TO der Apache Foundation die unter anderem Folgendes beschreibt:


Installing services
The safest way to manually install the service is to use the provided service.bat script. Administrator privileges are required to run this script. If necessary, you can use the /user switch to specify a user to use for the installation of the service.
NOTE: On Windows Vista or any other operating system with User Account Control (UAC) you must either disable UAC or right-click on cmd.exe and select "Run as administrator" in order to run this script. If UAC is enabled neither being logged on with an Administrator account, nor using the /user switch is sufficient.

Install the service named 'Tomcat7'
C:\> service.bat install
Also begeben wir uns auf der Windows command line in den Ordner, in dem Tomcat liegt, wechseln ins bin Verzeichnis und installieren das service wie beschrieben.
Konfigurieren kann man den service anschließend über das Gui, wenn man Tomcat7w.exe startet.


Konfiguration für den Jazz Team Server:

Um nun dem Jazz Framework mitzuteilen, welchen tomcat es wie verwenden soll,
klärt uns die Hilfe von RTC direkt von IBM auf:

Vorbereitende Schritte

Das Stammverzeichnis Ihrer Implementierung von Jazz Team Server muss C:\jazz sein. Passen Sie Ihre Pfade ausgehend vom tatsächlichen Stammverzeichnis an.
Bei diesen Anweisungen wird davon ausgegangen, dass der Dienst nicht installiert ist. 
Falls er installiert ist, überspringen Sie Schritt 1!


  1. Öffnen Sie eine Eingabeaufforderung und führen Sie den folgenden Befehl aus, um den Tomcat-Dienst zu installieren.
    C:\jazz\server\tomcat\bin\service.bat install
  2. Wenn Sie Derby als Datenbank für Ihr Jazz-Repository verwenden, bearbeiten Sie C:\jazz\server\conf\jazz\teamserver.properties so, dass für die Datenbankposition ein absoluter Pfad angegeben ist. Ändern Sie beispielsweise den Wert von db.jdbc.location in C:/jazz/server/repositoryDB.
    Beachten Sie die Schrägstriche (/) im Pfad.
    Zusätzlich zur Bearbeitung von teamserver.properties muss die Datei log4j.properties hinzugefügt werden, um den Protokolldateipfad zu einem absoluten Pfad zu machen. Bearbeiten Sie für die JTS-Protokolldatei conf\jts\log4j.properties, um den Wert der Eigenschaft log4j.appender.file.File auf einen absoluten Pfad zu setzen, z. B. auf log4j.appender.file.File=C:/jazz/server/logs/jts.log.
    Beachten Sie die Schrägstriche (/) im Pfad. 
  3. Führen Sie C:\jazz\server\tomcat\bin\tomcat5w.exe aus, um den Dienst zu konfigurieren. 
  4. Klicken Sie auf das Register Java und löschen Sie die Markierung im Kontrollkästchen Use default.
  5. Fügen Sie unter Java Virtual Machine den folgenden Pfad hinzu. Sie können auch auf die Auslassungspunkte klicken, um nach der Datei jvm.dll zu suchen.
    C:\jazz\server\jre\bin\j9vm\jvm.dll
  6. Fügen Sie unten im Textfeld Java Options die folgenden Zeilen hinzu:
    -DJAZZ_HOME=file:///c:/jazz/server/conf
    -Djava.awt.headless=true 
    -Dorg.eclipse.emf.ecore.plugin.EcorePlugin.doNotLoadResourcesPlugin=true 
    -Dcom.ibm.team.repository.tempDir=%TEMP%
    -Djazz.connector.sslProtocol=SSL_TLS
    -Djazz.connector.algorithm=IbmX509
    -Dlog4j.configuration=file:///c:/jazz/server/conf/startup_log4j.properties
    
    Wenn Sie eine Oracle-Datenbank verwenden, fügen Sie folgende Zeile hinzu:
    -DORACLE_JDBC=[Pfad zur Oracle JDBC-Treiber-JAR]
    
    Falls Sie eine SQL-Server-Datenbank verwenden, fügen Sie auch die folgende Zeile hinzu:
    -DSQLSERVER_JDBC=[Pfad für die SQL Server JDBC-Treiber-JAR]
  7. Ändern Sie die Größe für Maximum memory pool in den Wert '1200'.
  8. Vergewissern Sie sich, dass auf den Registerseiten Startup und Shutdown für Mode die Einstellung jvm ausgewählt ist.
  9. Klicken Sie auf das Register General und setzen Sie den Starttyp unter Startup type auf Automatic, sofern der Dienst automatisch beim Start von Windows gestartet werden soll.

  10. Starten Sie den Dienst, um den Server zu testen.


Damit sollte nun tomcat als Dienst unter Windows laufen und von Jazz verwendet werden.


Um den Jazz Team Server in einer vorhandener Apache-Tomcat-Umgebung implementieren habe ich folgende Informationen hier von IBM gefunden:

Verwenden Sie die folgenden Anweisungen, um Jazz Team Server für die Zusammenarbeit mit Ihrem vorhandenen Apache Tomcat Server zu konfigurieren.

Vorgehensweise

  1. Kopieren Sie die Datei 'jts.war' aus dem Jazz-Installationsverzeichnis in das Verzeichnis tomcat\webapps.
  2. Öffnen Sie die Tomcat-Startdatei zur Bearbeitung und fügen Sie die folgenden Einstellungen für Systemeigenschaften hinzu:
    set CATALINA_OPTS=-Dcom.ibm.team.server.configURL=file:///"%cd%"/teamserver.properties -Dlog4j.configuration=file:///"%cd%"/log4j.properties 
    set JAVA_OPTS=-Djava.awt.headless=true -DORACLE_JDBC="%ORACLE_JDBC%" -DDB2I_JDBC="%DB2I_JDBC%" -DDB2Z_JDBC="%DB2Z_JDBC%" -Dorg.eclipse.emf.ecore.plugin.EcorePlugin.doNotLoadResourcesPlugin=true -Dcom.ibm.team.repository.provision.profile="%cd%"\provision_profiles -Dcom.ibm.team.repository.tempDir=%TEMP% -Xmx700M
    
  3. Speichern und schließen Sie die Startdatei.
  4. Öffnen Sie die Datei teamserver.properties und bearbeiten Sie sie so, dass sie auf Ihre Datenbank zeigt.
  5. Speichern und schließen Sie die Datei teamserver.properties.



Doch leider weiß ich nicht, ob man für Jazz unbedingt den mitgelieferten tomcat verwenden MUSS oder einfach einen bereits installierten (in einer anderen Version?) verwenden kann.


Falls jemand hierbei Erfahrungen hat, bitte lasst es mich wissen, nutzt die Kommentar Funktion ;)