Laden...

Zwischenspeicher als Array realisieren?

Erstellt von nali vor 17 Jahren Letzter Beitrag vor 17 Jahren 1.896 Views
nali Themenstarter:in
40 Beiträge seit 2006
vor 17 Jahren
Zwischenspeicher als Array realisieren?

Hi,
ich habe folgendes Problem: ich möchte eine Art Zwischenspeicher, im folgenden Cache genannt, für eine regelmässig (jede Sekunde) eintreffende Wertemenge erstellen. Hierzu habe ich mir ein Objekt "Cache" erstellt, welches als Array über die zu speichernden Werte realisiert ist.


    public class Cache
    {
        private CacheItem[] speicher;
        // Wie groß wird der Cache
        int size = 0;
        // aktuelle Zelle im Array
        int last = 0;

        public Cache(int size)
        {
            this.size   	= size;
            speicher = new CacheItem[size];
            last        = 0;
        }

        public void addItem(CacheItem item)
        {
            // Überlauf?
            if (size == last)
            {
                last = 0;
            }
            //MessageBox.Show("Position: " + last);
            speicher[last] = item;
            last++;
        }
    }

Die Definition von CacheItem lass ich hier mal aussen vor, da sie wohl kein Problem darstellen wird. Nun folgende Idee hinter dieser Realisierung. Eine Cache wird mit einer festen Größe erstellt. Z.B. new Cache(30) - erzeugt mir einen Cache der 30 Elemente maximal aufnehmen kann. Wenn ich beim letzten Element des Array angekommen bin, möchte ich wieder von vorne anfangen, also das Element in der 1. Zelle überschreiben. D.h. der Zeiger "last" zeigt immer auf das aktuelle freie bzw. zu überschreibene Element im Array.

Leider habe ich jetzt das Problem, dass dieser Zugriff wohl nicht atomar läuft - wundert mich auch nicht wirklich. Die Funktion addItem() wird von verschiedenen externen Funktionen oftmals aufgerufen und es kommt vor, dass ein Element eingefügt wird, das nächste Element diese Element aber direkt überschreibt - weil der Zeiger last nicht schnell genug um eins erhöht wird/wurde.

Daher meine prinzipielle Frage: ist die Implementierung mittels Arrays hier überhaupt sinnvoll? Ich bräuchte sowas wie eine undliche bzw. zyklische Liste fester Länge. Sobald ich ans Ende angekommen bin, sollte wieder vorne eingefügt bzw. überschrieben werden.

Was meint Ihr - ist mein Ansatz zu gebrauchen oder soll ich eine andere Datenspeicherungmethode wählen?

P.S: Als Diagnose haben ich den(auskommentierten) MessageBox-Dialog genutzt. Lass ich mein Programm laufen kommen dann Dialogboxen mit

Position: 0
Position: 0
Position: 0
Position: 1
Position: 1
Position: 1
Position: 2
usw...

d.h. jede Zelle wird 3 mal hintereinander beschrieben.

2.223 Beiträge seit 2005
vor 17 Jahren

moin,

ein problem kann ich nicht wirklich finden

entweder machste das ganze mal step für step im debugger oder du mußt noch mehr code posten

mfg

V
52 Beiträge seit 2006
vor 17 Jahren

hi

also den grund dafür das alles erst dreimal geschrieben wird bevor es gändert wird hab ich auch nich gefunden, aber ich würde statt eines arrays eine Liste nehmen.

Da kannst du dann einfach immer ein elment anhängen (add) und danach gucken wie viele elmente gerade drin sind (count) und dann evtl am anfang welche abschneiden (RemoveRange(0, Count - Max))

gruß v1vec

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo nali,

Ich bräuchte sowas wie eine undliche bzw. zyklische Liste fester Länge

Queue kommt dem am nächsten.

Leider habe ich jetzt das Problem, dass dieser Zugriff wohl nicht atomar läuft

Die Zugriffe musst du natürlich synchronieren. Bei der Nicht generischen Queue-Klasse geht das einfach durch die verwendung von Queue.Synchronized, sonst mit lock.

herbivore

nali Themenstarter:in
40 Beiträge seit 2006
vor 17 Jahren

Hi,
habe mal versucht eure Ideen umzusetzen.
Also weder mit lock noch mit Listen oder synchronisierten queues kann ich das Problem lösen.
Auch hier werden die Einträge mehrfach eingefügt - nur halt jetzt nicht mehr in meiner Klasse Cache, sondern eben in eine Liste oder Queue.

Das verwirrt mich jetzt sehr, wie kann sowas sein?
Es muss also das "Einfügen" mehrfach und zeitgleich ausgeführt worden sein.
Aber sowas wie lock() verhindert doch gerade, dass ein solches Einfügen von einem anderen Einfügen unterbrochen wird.

Ich bin jetzt echt verwirrt - werde mir die Sache morgen nochmal in Ruhe anschauen.
Falls ich keine Lösung finden werden (wovon ich ausgehe), werde ich wohl noch etwas Code posten, um die aufrufenden Funktionen zu beschreiben.

B
1.529 Beiträge seit 2006
vor 17 Jahren

Mir sind ein paar Sachen aufgefallen.
1.) Cache.size bleibt bei dir immer 0, da die Zeile size = size; im Konstruktor nur die lokale Variable ändert (bzw. gleichlässt).
2.) Wenn du auf Überlauf testest, solltest du mit if (last ≥ size) testen.
3.) Momentan würdest du in eine Exception laufen (Array Index out of range).
4.) Du benötigst size gar nicht, das du ja speicher.Length abfragen kannst.
5.) speicher sollte nicht public sein. Schreib dir zwei public Methoden, die Werte lesen und schreiben. Wenn du magst, kannst du dazu auch setter und getter nutzen.
6.) Innerhalb dieser Methoden (bzw. setter und getter) sollte dann ein lock auftauchen.

Nichtsdestotrotz wird das das dreimalige Hinzufügen nicht von dieser Klasse hervorgerufen. Da ist wohl noch etwas Code von Nöten.
Trotzdem würde ich dir zu einer Queue anstelle eines Arrays raten.

308 Beiträge seit 2005
vor 17 Jahren

Prinzipiell finde ich deinen ansatz mit dem array ziemlich gut. bei der geringen und bekannten anzahl von elementen macht das schon sinn.

Herbivore hat schon recht, dass das problem mittels synhronisierung in den griff zu bekommen sein sollte.

so lief es bei mir:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    public class CacheItem
    {
    }

    public class Cache
    {
        public CacheItem[] speicher;
        // Wie groß wird der Cache
        int size = 0;
        // aktuelle Zelle im Array
        int last = 0;

        public Cache(int size)
        {
            this.size = size;
            speicher = new CacheItem[size];
            last = 0;
        }

        public void addItem(CacheItem item)
        {
            lock(this)
            {
                // Überlauf?
                if (size == last)
                {
                    last = 0;
                }
                System.Console.Out.WriteLine(String.Concat("Position: ", last.ToString()));
                speicher[last] = item;
                last++;
            }
        }
    } 

    class Program
    {
        static Cache g_Cache = new Cache(30);
        static void doTest()
        {
            while(true)
                g_Cache.addItem(new CacheItem());
        }
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                Thread testRunner = new Thread(new ThreadStart(doTest));
                testRunner.Start();
            }
            Thread.Sleep(10000);
        }
    }
}

(bitte nicht meckern, das die threads nicht sauber beendet werden... ging nur ums prinzip...)

Was mir (und vor allem dem compiler) noch aufgefallen ist die zuweisung
size = size im constructor.
wenn du ein feld hast, das genauso heisst wie ein paramater in einer funktion musst du ein this. for den feldnamen setzen.
Allerdings verstehe ich deine ausgabe dann auch nicht so ganz, da dein size feld immer null war (auch wenn das array die größe von size hatte) und somit eigentlich immer ein überlauf hätte statfinden müssen....

B
1.529 Beiträge seit 2006
vor 17 Jahren

Der Überlauf fand nur einmal statt.
Beim ersten Durchlauf ist last == size.
Daher wird last auf 0 gesetzt (war es aber vorher schon).
Anschliessend inkrementiert (jetzt == 1).
Alle folgenden Vergleiche liefern daher false.
Daher: beim Überlauf immer mit ≤ oder ≥ testen. Im günstigsten Fall ist es unnötig, im schlimmsten Fall verhindert es schwer zu findene Fehler.

Wenn dir die Variante mit der Queue wirklich nicht gefallen sollte, solltest du aber zumindest darüber nachdenken, mit deinem Array einen Ringpuffer zu basteln.

nali Themenstarter:in
40 Beiträge seit 2006
vor 17 Jahren

Original von Borg
Mir sind ein paar Sachen aufgefallen.
1.) Cache.size bleibt bei dir immer 0, da die Zeile size = size; im Konstruktor nur die lokale Variable ändert (bzw. gleichlässt).
2.) Wenn du auf Überlauf testest, solltest du mit if (last ≥ size) testen.
3.) Momentan würdest du in eine Exception laufen (Array Index out of range).
4.) Du benötigst size gar nicht, das du ja speicher.Length abfragen kannst.
5.) speicher sollte nicht public sein. Schreib dir zwei public Methoden, die Werte lesen und schreiben. Wenn du magst, kannst du dazu auch setter und getter nutzen.

Punkt 1) und 5) sind in der Tat fehlerhaft, aber auch nur hier im Beitrag - habe wohl eine alte Fassung in den Beitrag kopiert. Habe den Beitrag entsprechend korrigiert.

Punkt 2-4 sind ebenfalls gute Hinweise, habe ich in der Eile auch nicht bedacht.
Aber wie du selber schon sagetest - daran dürfte das 3-malige Aufrufen nicht liegen.

Ich werde die Korrekturen einarbeiten und den Quelltext nochmal durchsehen,
dann melde ich die Ergebnisse hier.

nali Themenstarter:in
40 Beiträge seit 2006
vor 17 Jahren

So habe jetzt lange rumgesucht und den "Schuldigen" offenbar gefunden.
Es liegt wohl nicht an der Implementierung meiner Liste.
Der Liste wird ein Element zugefügt, wenn mein EventHandler "alarm" schlägt:


// Zur Diagnose einen Zähler
int count = 0;
 void pGPS_NewGPSFix(object sender, PocketGpsLib.GPSHandler.GPSEventArgs e)
        {
           // Zur Diagnose
           count++;
           MessageBox.Show("Count: " + count);
           ...
           // Neue Element in den Zwischenspeicher einfügen
           cache.addItem(item);
           ...
        }

Dabei ist mir jetzt aufgefallen, wenn ich in diesem EventHandler mal wieder meine beliebte Diagnose-MessageBox aurufe bekomme ich solche Werte.

Count: 1
Count: 1
Count: 1
Count: 2
Count: 2
Count: 2
usw...

Also der Fehler wie ganz zu Beginn, nur schon früher - außerhalb des Zwischenspeichers.

Ich verwende eine externe GPS-Bibliohtek, die offenbar auch mit Threads arbeitet.
Ich kenne mich jetzt in dem Bereich Threads überhaupt nicht aus - habe aber mal die Suche des Forums bemüht und festgestellt, dass das wohl offenbar mit Threads zusammenhängt.

Hier ist PocketGPSLib zu laden inklusive Quellcode: PocketGPS
In der Datei "GPSHandler.cs" in Zeile 40-42 steht irgendwas mit Threads,
das dürfte wohl das "Problem" sein (bin mir sicher Threads mache ich dem Zusammenhang wohl Sinn).

Also verstehe ich das richtig: PocketGPS startet mehrere Threads die alle das Event abfeuern was ich mit pGPS_NewGPSFix(...) abfange und das passiert irgendwie gleichzeitig und deshalb behalten Variablen den selben Wert bzw. ändern sich nicht passend? Das dürfte ja erklären warum ich "Count: 1 Count: 1 Count:1 Count 2: usw" ausgegeben bekomme.

Wie löse ich das Problem, habe mal probiert um das "cache.AddItem" ein lock(this) zu setzen, dass hat aber gar nichts gebracht.
Das gefällt mir nicht - was kann man da tun. Semaphore/Mutex?
Ich müsste ja nur eine Variable haben, die den Zugang zu diesem Codeabschnitt Thread-Sicher regelt.

Wäre dankbar, wenn jemand eine Idee hätte - mein gesamtes Projekt ist nämlich zum Stillstand gekommen - ohne diesen Cache kann ich leider nicht weiterarbeiten.
Und das der Fehler offenbar (IMHO) an einer externen Sache/Code liegt, macht
die Sache nur noch schlimmer.

P.S: Bitte für Codehinweise beachten - ich nutze das .NET CF Framework - ich glaube da gibts keine Semaphore?

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo nali,

dann hat das aber doch nichts mit Synchronisation zu tun, sondern damit, dass du den EventHandler dreimal registriert hast. Verhindere das und alles ist gut.

herbivore

X
1.177 Beiträge seit 2006
vor 17 Jahren

Hoi nali,

hab sowas neulich mal gemacht (6 Monate oder so) - habe einfach eine dumme Liste benutzt und immer im Add() den ersten Eintag gelöscht, solange die Liste mehr als X Einträge hatte.

Die herangehensweise ist eigentlich egal, zu deinem Prob mit mehr Einträgen muss ich leider herbivore zustimmen - es stimmt was anderes nicht.

🙂

cu xynratron

Herr, schmeiss Hirn vom Himmel - Autsch!

Die Erfahrung zeigt immer wieder, dass viele Probleme sich in Luft auslösen, wenn man sich den nötigen Abstand bzw. Schlaf gönnt.

nali Themenstarter:in
40 Beiträge seit 2006
vor 17 Jahren

Nun ja,
herbivore hatte recht. Ich hinge definitiv 3x an dem EventHandler, wenn auch
"versteck", so dass es nicht wirklich schnell zu sehen war.

Das erklärt natürlich einiges, das hat man davon - den Fehler überall
suchen, nur nicht da, wo es eigentlich offensichtlich ist.

Ich danke euch für die zahlreichen Vorschläge.
Verwende jetzt auch eine Liste anstelle des Arrays
um die Einträge zu speichern. Das lässt sich im Nachhinein wesentlich leichter
durchlaufen.

Danke
Christian