Wie bereits im vorherigen Beitrag als Idee angesprochen, habe ich die Möglichkeit der dynamischen Anpassung des Abfrage-Intervalls der SharedMemoyDependency-Klasse umgesetzt. Um diese Idee umzusetzen mussten einige Änderungen sowohl in der SharedMemory-Klasse als auch in der SharedMemoryDependency-Klasse eingebracht werden. Im Zuge der Änderungen habe ich die Methode AddObject() der SharedMemory-Klasse in InsertObject() umbenannt. Meiner Meinung nach beschreibt der neue Methodenname die Funktion der Methode besser da ja kein Objekt einer bestehenden Auflistung hinzugefügt, sondern in das SharedMemory-Segment eingefügt wird. Dabei wurde auch gleich für die beiden Methoden InsertObject(object) und GetObject() eine neue Überladung mit einem zusätzlichen Parameter von Typ bool eingefügt. Der zusätzliche Parameter autoLock legt fest ob die Sperre des Mutex der Klasse von der jeweiligen Methode verwaltet wird, oder vom Aufrufer der Methode. Auch habe ich die InsertObject()-Methode dahingehend Überarbeitet, dass jetzt nur noch alle 200 Millisekunden ein Objekt in das SharedMemory-Segment eingefügt werden kann. Sollte der Zeitintervall seit dem letzten Einfügen kürzer sein, wird in einer Schleife die Methode blockiert, bis der Zeitintervall erreicht ist. Eine ähnlich Begrenzung als minimalen Zeitintervall zwischen zwei Abfragen wird auch in der SharedMemoryDependency-Klasse verwendet. Hier wird der Zeitraum mittels einer Konstanten auf 20 Millisekunden begrenzt. Dies sind Werte die ich für sinnvoll erachte. Bei kleinen Objekten im SharedMemory-Segment können kürzere Intervalle vielleicht Sinn machen. Doch in der Regel sind es meist größere und komplexere Objekte die zwischen zwei Prozessen ausgetauscht werden sollen und ein DataSet oder ähnliches Objekt alle 200 Millisekunden zu aktualisieren müsste mehr als ausreichend sein. Im folgenden Diagramm sind noch einmal die Klassen SharedMemory und SharedMemoryDependency sowie die verwendetet Enumeration zu sehen.

SharedMemory Klassendiagramm

In der Klasse SharedMemory ist die neue Eigenschaft SerializedObjektSize vom Typ Int32 verfügbar. Mit ihr kann die serialisierte Größe des Objekts im Speicher abgerufen werden. Diese ist in sofern von Interesse, da die Größe eines serialisierten Objekts doch sehr stark von der Größe des Objekts vor der Serialisierung abweicht. Der Typ Int32 benötigt normalerweise 4 byte auf einer x86 Plattform. Serialisiert belegt ein Int32-Wert 54 byte im Speicher auf derselben Plattform. In der Regel braucht sich nicht um die Größe des Objekts welches im SharedMemory-Segment gespeichert werden soll gekümmert zu werden, da die benötigte Größe von der Methode InsertObject(object) automatisch ermittelt wird. Sollte doch einmal die serialisierte Größe eines Objekts ermittelt werden müssen, stehen hierfür die statischen Methode GetMinMemorySize(object) und GetOptMemorySize(object) zur Verfügung. Die erste Methode gibt die genaue serialisierte Größe des Objekts zurück. Die zweite Methode gibt die Größe optimiert auf 8 byte Blöcke zurück wobei der zurückgegebene Wert immer auf auf die nächsten 8 byte aufgerundet wird. Im Falle eines In32-Wertes gibt die erste Methode 54 und die zweite Methode 56 byte zurück. Die Größe des Objekts, welches im SharedMemory-Segment gespeichert werden kann wurde auf 500 Mb begrenzt. Größere Objekte dürften nur in den wenigsten Fällen benötigt werden. Auch gibt es für größere Objekte bessere Methoden wie etwa die Verwendung eines Memory-mapped File.

Soweit zu den Anpassungen und nun zur eigentlichen Dynamisierung des Abfrage-Intervalls der SharedMemoryDependency-Klasse. Das ganze kann nur funktionieren, wenn ein Verkürzen des Intervalls sofort geschieht und die Verlängerung in Abständen vorgenommen wird. Das es geklappt hat kann man an folgendem Video sehen.

private void CheckSegment(object state)
{
    // den aufrufenden Timer als neue Instanz erzeugen.
    Timer stateTimer = (Timer)state;
    // den aktuellen Wert aus dem Schlüssel-Segment lesen
    int i = this.segment.GetStatus();
    // wenn Rückgabewert 1, würde das Objekt im
    // SharedMemory Segment geändert
    if (i == 1)
    {
        // prüfen ob autoAdjust aktiv ist
        if (this.autoAdjust)
        {
            // prüfen ob die stopWatch läuft.
            if (this.stopWatch.IsRunning)
            {
                // stopWatch anhalten
                this.stopWatch.Stop();
                // und die verstrichene Zeit in Millisekunden an die
                // Optimierungs-Methode übergeben
                this.OptimizeIntervall(this.stopWatch.ElapsedMilliseconds);
                // und die geänderte pollTime einstellen
                stateTimer.Change(0, this.pollTime);
            }
        }
        // das Objekt aus dem SharedMemory-Segment holen
        object data = this.segment.GetObject(true);
        // EventHandler initialisieren
        EventHandler<SharedMemoryNotificationEventArgs> handler = this.OnChanged;
        if (handler != null)
        {
            // Event auslösen
            handler(this, new SharedMemoryNotificationEventArgs(
                data, this.segment.SerializedObjectSize, this.pollTime));
        }
        // Änderung signalisieren
        this.hasChanged = true;
        // wenn autoAdjust aktiv
        if (this.autoAdjust)
        {
            // stopWatch rücksetzen und neu starten.
            this.stopWatch = Stopwatch.StartNew();
        }
    }
    else if (i == -1)
    {
        // Objekt nicht im Speicher vorhanden oder es kann nicht
        // darauf zugegriffen werden.
        // Den aufrufenden Timer zerstören
        stateTimer.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.");
    }
}

Nachdem die Ermittlung der benötigten Zeit zwischen zwei Änderungen geklärt ist, kommt jetzt die Optimierung des Zeitintervalls an die Reihe. Um den ganzen Vorgang erst einmal zu starten, wird beim Initialisieren der Klasse der kleinste erlaubte Intervall von 20 Millisekunden verwendet. Bei Eintritt der ersten erkannten Änderung, liegt jetzt ein Zeitraum vor. Als Intervall für die Abfragen auf Änderungen, habe ich 10% des verstrichenen Zeitraums veranschlagt; also theoretisch immer 10 Abfragen zwischen zwei erkannten Änderungen. 10 Abfragen deshalb, um auch auf einen kürzeren Zeitraum zwischen zwei Änderungen regieren zu können. Zunächst wird also ein temporärer Intervall aus dem verstrichenen Zeitraum berechnet. Jetzt wird geprüft ob dieser temporäre Intervall kleiner als der aktuelle und größer als der minimal erlaubte Intervall ist. Sollte diese Bedingung erfüllt sein, wird der temporäre Intervall als neue Polltime verwendet und somit der Abfrageintervall sofort verkürzt. Um auch die maximale Polltime zu verringern, wird nach dreimaligem Unterschreiten der maximalen Polltime der Wert dieser mit dem gerade ermittelten Intervall ersetzt. Sollte der temporäre Intervall größer als der maximal ermittelte Intervall sein, wird nach dreimaligem überschreiten des Maximalwertes der Wert neu gesetzt. Somit ist gewährleistet, dass eine Verkürzung des Intervalls sofort eintritt und eine Verlängerung erst nach mehrmaligem Überschreiten des Maximalwertes. Hier nun das Listing der Methode OptimizeIntervall:

private void OptimizeIntervall(long intervall)
{
    // prüfen ob der Parameter intervall eine zulässige Größe aufweist.
    if (intervall > (long)int.MaxValue)
    {
        // intervall ist zu groß für einen int32-Wert.
        // auf den größtmöglichen Integerwert setzen.
        intervall = (long)int.MaxValue;
    }
    // intervall in einen int32-Wert casten.
    int tempTime = (int)intervall;
    // temporäre Polltime erstellen mit theoretisch 10 Abfragen
    // zwischen zwei erkannten Änderungen
    int tempPollTime = tempTime / 10;
    // prüfen ob der aktuelle Intervall größer als der minimale Intervall ist
    if (this.pollTime == minPollTime)
    {
        // aktuellen Intervall auf ermittelten Intervall setzen
        this.pollTime = tempPollTime;
    }
    // prüfen ob die temporäre Polltime kleiner als die aktuelle Polltime
    // und größer als der minimal erlaubte Intervall
    if (tempPollTime < this.pollTime && tempPollTime > minPollTime)
    {
        // tempPollTime ist kleiner, pollTime auf aktuellen Wert setzen.
        this.pollTime = tempPollTime;
        // prüfen wie oft der Intervall schon kleiner war
        // als die maxPollTime
        // Wenn der Intervall bereits 3 mal kleiner war,
        if (this.minTimeCounter >= 3)
        {
            // maxPollTime auf aktuellen Wert als neuen Vergleich setzen
            this.maxPollTime = tempPollTime;
            // Counter zurücksetzen
            this.minTimeCounter = 0;
        }
        else
        {
            // Counter erhöhen
            this.minTimeCounter++;
        }
    }
    // prüfen ob der aktuelle Intervall größer ist als
    // der größte gemessene Intervall.
    if (tempPollTime > this.maxPollTime)
    {
        // prüfen wie oft der Intervall schon größer war
        // als die maxPollTime.
        // Wenn der Intervall bereits 3 mal größer war,
        if (this.maxTimeCounter >= 3)
        {
            // maxPollTime auf aktuellen Wert als neuen Vergleich setzen
            this.maxPollTime = tempPollTime;
            // aktuelle pollTime auf aktuellen Wert setzen
            this.pollTime = tempPollTime;
            // Counter zurücksetzen
            this.maxTimeCounter = 0;
        }
        else
        {
            // Counter erhöhen
            this.maxTimeCounter++;
        }
    }
}

Im angehängten Demo sowie im obigen Video kann man sehr gut erkennen wie die Dependency-Klasse auf  Veränderungen des Intervalls zwischen den Änderungen im SharedMemory-Segment reagiert. Natürlich können in der vorliegenden Lösung auch feste Abfrageintervalle verwendet werden. Es braucht im Konstruktor nur ein Wert größer 0 angegeben zu werden.

IpcTest_Dynamic_PollTime

Technorati-Tags: | | |
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitten "kicken" sie ihn.
kick it on dotnet-kicks.de