Im vorherigen Artikel habe ich die Verwendung eines SharedMemory Segment mit Hilfe zweier Konsolenanwendungen gezeigt. Dort wurde in der Client-Anwendung reagiert, indem ein Objekt aus dem SharedMemory-Segment geladen und mit einem gespeicherten Objekt verglichen wurde. Das funktioniert ja mit kleineren Objekten im Shared Memory ganz gut. Doch was ist wenn eine größere DataTable oder gar ein mehre Mb großes DataSet, oder ein ähnlich komplexes Objekt, im SharedMemory gespeichert ist? Allein das laden der Objekte zum vergleichen, sowie der Vergleich an sich dürften sich auf die Reaktionsgeschwindigkeit dieser Anwendung sehr nachteilig auswirken. Zur Lösung dieses Problems bietet sich eine Dependency-Klasse, ähnlich der SqlDependency-Klasse, an. Nur wie muss die Implementierung solch einer Dependency-Klasse aussehen? Die grundlegenden Funktionen sollten sein:
- Einen Mechanismus zur Benachrichtigung des Abonnenten.
- Der Abonnent braucht keine Kenntnis vom zugrundeliegenden SharedMemory Segment zu haben.
- Der Intervall in dem das SharedMemory Segment abgefragt wird muss einstellbar sein.
- Der Benachrichtigung's-Mechanismus sollte das geänderte Objekt aus dem SharedMemory Segment liefern.
- Es sollte nur der Namen des zu überwachenden SharedMemory Segments angegeben werden müssen.
- Eine Möglichkeit das SharedMemory Segment in sehr kurzen Abständen auf Änderungen zu überprüfen.
Um den letztgenannten Punkt zu realisieren habe ich das Konzept der SharedMemory-Klasse noch einmal dahingehen überarbeitet, so dass jetzt zwei Segmente erzeugt werden. Zusätzlich zum bereits bestehenden Segment wird jetzt ein zweites Segment erzeugt, welches lediglich einen Integer-Wert speichert der den Status des Daten-Segments darstellt. Beim speichern eines Objekts in das Daten-Segment wird der Wert 1 in das Schlüssel-Segment geschrieben, welcher eine Änderung darstellt. Wird nun das Objekt von der GetObjekt() Methode abgerufen, schreibt diese den Wert 0 in das Schlüssel-Segment und kennzeichnet es so als gelesen. Solange der Abfrage-Mechanismus nun den Wert 0 liest, brauchen die Daten nicht aus dem Daten-Segment abgerufen zu werden. Das Abrufen des Integer-Wertes aus dem Schlüssel-Segment geht wirklich sehr schnell. Real weit unter einer Millisekunde. Dadurch lasse sich sehr kurze Abfrage-Intervalle realisieren. Um zu zeigen wie schnell die Dependency-Klasse auf Änderungen im Daten-Segment reagiert, habe ich ein Demo erstellt in dem in zufälligen Intervallen in das Daten-Segment geschrieben wird. Hier vorab ein kleines Video:
IpcTest mit Dependency-Klasse
Die Verwendung der SharedMemoryDependency-Klasse ist denkbar einfach. Sie braucht lediglich mit dem Namen des zu überwachenden SharedMemory Segments und optional einem Abfrage-Intervall initialisiert zu werden. Entweder wird ein EventHandler Delegat mit dem OnChanged Ereignis abonniert, oder es wird die Eigenschaft HasChanged überwacht. Die eigentliche Überwachung wird mit der Methode Start() gestartet. Mit der Eigenschaft Enabled kann vorher noch geprüft werden, ob bereits eine Überwachung aktiv ist. Hier ein Beispiel mit einem Abonnement des OnChanged Ereignis:
// neue Instanz der SharedMemoryDependency initialisieren
// mit einem Abfrage-Intervall von 15 Millisekunden
SharedMemoryDependency dependency = new SharedMemoryDependency("testing", 15);
// OnChanged Eventhandler registrieren
dependency.OnChanged +=
new EventHandler<SharedMemoryNotificationEventArgs>(DependencyOnChanged);
// prüfen ob dependency bereits gestartet wurde
if (!dependency.Enabled)
{
// dependency jetzt starten
dependency.Start();
}Beim registrieren des EventHandler OnChanged kann automatisch ein Delegat erzeugt werden. In der Methode kann das geänderte Objekt aus dem Daten-Segment direkt als Eigenschaft des SharedMemoryNotificationEventArgs-Arguments empfangen werden.
static void DependencyOnChanged(object sender, SharedMemoryNotificationEventArgs e)
{
Console.WriteLine(" lese: " + e.ChangedContent.ToString());
}Soweit zur Verwendung der SharedMemoryDependency-Klasse in einer Client-Anwendung.
Wie bereits oben schon angesprochen, habe ich die SharedMemory-Klasse überarbeitet um das Zusammenspiel mit der SharedMemory-Klasse überhaupt zu ermöglichen. Um den aktuellen Status eines Objekts im Speicher abfragen zu können, wurde die interne Methode GetStatus() hinzugefügt. Diese gibt, ähnlich einem HRESULT, einen von drei möglichen Integer-Werten zurück. Wobei -1 für "Objekt nicht gefunden" steht. 0 wird zurückgegeben wenn keine Änderungen vorliegen und 1 wenn das Objekt im Speicher geändert wurde. Ich habe die Methode mit dem internal Schlüsselwort versehen, da diese Methode nur intern von der SharedMemoryDependency-Klasse verwendet werden soll. Diese Methode macht nichts anderes, als den Wert aus dem angesprochenen zweiten SharedMemory Segment zu lesen und zurück zugeben.
internal int GetStatus()
{
// Rückgabewert mit -1, Objekt nicht gefunden, initialisieren
int value = -1;
// MemoryStream zum aufnehmen des Status erzeugen
MemoryStream keyStream = new MemoryStream();
// das Status-Objekt in den Stream schreiben
CopySharedMemoryToStream(keyStream, true);
// einen Formatter zum deserialisieren erzeugen
BinaryFormatter formatter = new BinaryFormatter();
try
{
// Den Rückgabewert in einen Int-Wert parsen value = int.Parse(
formatter.Deserialize(keyStream).ToString(),
CultureInfo.InvariantCulture);
}
catch (SerializationException)
{
// Wahrscheinlich kein Objekt im Stream.
// Fehler bei der Deserialisierung abfangen
// um -1 zurückgeben zu können.
}
return value;
}In der SharedMemoryDependency-Klasse wird genau diese Methode in einem TimerCallback-Delegaten verwendet um auf Änderungen im Speicher zu reagieren. Wenn der TimerCallback-Delegate den Wert 0 liest erfolgt keine Aktion. Liest der Delegat den Wert 1 liest, wird das geänderte Objekt aus dem SharedMemory Segment gelesen. Anschließend wird ein neuer EventHandler-Delegate erzeugt. Diesem Delegaten wird eine Instanz der SharedMemoryNotificationEventArgs-Klasse mit dem Objekt aus dem SharedMemory Segment erzeugt und das Ereignis OnChanged ausgelöst sowie die Eigenschaft HasChanged auf true gesetzt. Somit wird dem Abonnenten mitgeteilt, das eine Änderung im SharedMemory Segment vorliegt. Sollte der Delegat den Wert -1, also Objekt nicht gefunden, lesen wird der Aufrufende Timer zerstört sowie der Wert der Eigenschaft Enabled auf false gesetzt und somit die Überwachung als beendet gekennzeichnet. Hier ein Listing des Delegaten:
private void CheckSegment(object state)
{
// den aktuellen Wert aus dem Schlüssel-Segment lesen
this.segment.Lock();
int i = this.segment.GetStatus();
this.segment.Unlock();
// wenn Rückgabewert 1, würde das Objekt im
// SharedMemory Segment geändert
if (i == 1)
{
// das Objekt aus dem SharedMemory-Segment holen
this.segment.Lock();
object data = this.segment.GetObject();
this.segment.Unlock();
// EventHandler initialisieren
EventHandler<SharedMemoryNotificationEventArgs> handler = this.OnChanged;
if (handler != null)
{
// Event auslösen
handler(
this,
new SharedMemoryNotificationEventArgs(data));
}
// Änderung signalisieren
this.hasChanged = true;
}
else if (i == -1)
{
// Objekt nicht im Speicher vorhanden oder es kann nicht
// darauf zugegriffen werden.
// Den aufrufenden Timer zerstören
Timer t = (Timer)state;
t.Dispose();
// Überwachung als beendet signalisieren
this.watching = false;
// Ausnahme auslösen
throw new SharedMemoryException(
"Auf das Schlüssel-Segment im Speicher kann nicht zugegriffen werden.");
}
}Die Umsetzung der Dependency-Klasse ist somit erfolgreich abgeschlossen. Für die Zukunft währe eine dynamische Anpassung des Abfrage-Intervalls, basierend auf den Intervallen zwischen den erkannten Änderungen im SharedMemory Segment, eine nützliche Änderung. Somit bräuchte sich um die Einstellung des Abfrage-Intervalls nicht mehr gekümmert zu werden. Falls gewünscht könnte der Abfrage-Intervall mit 0 initialisiert werden und um den Rest kümmert sich die SharedMemoryDependency-Klasse. Vorschläge und Kritiken, positive als auch negative, sind ausdrücklich erwünscht.
IpcTests_dependency
Wer bereits das Demo-Projekt aus dem vorherigen Artikel verwendet, sollte die Klassen NativeMethods und SharedMemory mit den Klassen aus diesem Demo ersetzen. Sollte jemand dieses Projekt oder Teile daraus in einem eigenen Projekt verwenden wollen, sollte er unbedingt die Klassen aus dem aktuellen Projekt verwenden. Dieses Demo und alle zukünftigen stehen unter der MIT-Lizenz. Die Modifizierung des Original-Codes von Richard Blewett ist mit ihm abgeklärt und wurde von ihm abgesegnet.
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitte "kicken" sie ihn.
