Ringbuffer implementation
-
Hallo Freunde,
ich hab für meine zwecke nen RingPuffer (ne endlos doppelverkette liste) geschrieben. Wird verwendet für asyncrone Datenübertragung übers Net als Send Buffer, zum Pollen.
public class CycleBuffer<K, T> { class CycleElem { //Members not hidden, because of nestes Elem Class //Elem Values public T m_Dat; public K m_Key; //References of next Elemnts public CycleElem m_Prev = null; public CycleElem m_Next = null; //Konstruktor public CycleElem(K Key, T Dat) { m_Dat = Dat; m_Key = Key; } } //Current Cycle Elem Position CycleElem m_objPos = null; //Syncronisation- object for Multithreading object objLock = new object(); //Remember Elem Count int m_iCount = 0; //Property returns the Object at current Cycle Position public T Value{ get { return m_objPos == null ? default(T) : m_objPos.m_Dat; } } //Move one elem forward public bool Prev() { lock (objLock) { return (m_objPos = (m_objPos != null) ? m_objPos.m_Next : m_objPos) !=null; } } //Move one elem backward public bool Next() { lock (objLock) { return (m_objPos = (m_objPos != null) ? m_objPos.m_Prev : m_objPos) !=null; } } //Check if Key exists public bool ContainsKey(K Key) { return this[Key] != null; } //Add ne Elem public T Add(K Key, T dat) { if(ContainsKey(Key)) return default(T); lock (objLock) { //Elems = 0 if (m_objPos == null) { m_objPos = new CycleElem(Key, dat); m_objPos.m_Next = m_objPos; m_objPos.m_Prev = m_objPos; } //Elems > 0 else { CycleElem tmp = new CycleElem(Key, dat); tmp.m_Prev = m_objPos.m_Prev; // C m_objPos.m_Prev.m_Next = tmp; // D m_objPos.m_Prev = tmp; // A tmp.m_Next = m_objPos; // B // m_objPos = tmp; //Elem Postion at ne added Elem } //Inkrement Elem counter ++m_iCount; } return dat; } //Get Value from Elem with specified Key public T this[K Key] { get { lock (objLock) { for (CycleElem e = m_objPos; e != null || e.m_Next != m_objPos; e = e.m_Next) { if (e.m_Key.Equals(Key)) return e.m_Dat; } } return default(T); } } //Delete Elem from Buffer public void Del() { lock (objLock) { // One or less elems exists set Pointer(Ref) to null if (Count <= 1) { m_objPos = null; m_iCount = 0; } // Two or more elems else { CycleElem tmp = m_objPos.m_Prev; tmp.m_Next = m_objPos.m_Next; m_objPos.m_Next.m_Prev = tmp; m_objPos = m_objPos.m_Next; --m_iCount; } } } // Get elemen Count public int Count { get { return m_iCount; } } // Check if buffer Empty public bool IsEmpty { get { return m_iCount == 0 && m_objPos == null; } } }
Würde mich über Feedback und Kritik freuen, da ich noch nich lang mit C# und Exceptions bewandt bin, frage ich mich wo hier am ehesten exception abgefangen werden müssten!? grüße
-
Auf den ersten Blick sieht es in Ordnung aus, aber es gibt einige Probleme. Vermutlich hättest Du noch getestet und einige davon gefunden (spätestens im Produktivbetrieb
)
Man hätte intern eine existierende Datenstruktur verwenden können, z.B. List<T>, hängt aber sicherlich mit deinen Anforderungen an die Performance zusammen.
Das ist mir aufgefallen:
if(ContainsKey(Key)) return default(T);
Warum default(T)? Ich würde sowas wie eine KeyExistsException werfen, oder überschreiben, je nach Anforderung. Ansonsten denkt der Benutzer deiner Klasse, dass es geklappt hat. => Fehlerquelle.
Auch hier:
public T Value{ get { return m_objPos == null ? default(T) : m_objPos.m_Dat; } }
Man kann nicht unterscheiden ob die Liste leer ist oder default(T) an der aktuellen Position steht.
IsEmpty und Count==0 sollten das gleiche zurückgeben, das tun sie glaube ich nicht immer, weil isEmpty mehr macht.
Ich würde mir mal die Thread-Sicherheit nochmal genauer angucken. Etwa hier:
public T Add(K Key, T dat) { if(ContainsKey(Key)) return default(T); lock (objLock) { ....
Hier kann es passieren, dass zwei Treads gleichzeitig prüfen ob Key X schon drin ist (was nicht der Fall war), danach würden sie beide (nacheinander) in den gelockten Bereich eintreten und du hättest zwei Elemente mit gleichem Key X.
Dann: Einer fragt Value ab, und der andere macht gerade Next()! Der erste darf sich nicht mehr darauf verlassen, das Value das gleiche ist wie vorher. Auch kann er nicht sichersein, dass wenn er immer Next() sagt und dann Value abfragt, er auch jedes Element durchläuft! Diese Navigation würde ich aus der Klasse rausnehmen. Nicht umsonst dürfen in bei Iterationen z.B. über List<T> die Datenstrukturen nicht verändert werden.
Wenn du die Klasse produktiv einsetzen willst, rate ich viele viele Unit-Tests dafür zu schreiben. Auch "Last"-Tests, also viele Treads die _sehr_ oft/gleichzeitg mal zufällig was einfügen/löschen/abfragen, mal was festgelegtes, etc. Am besten mit 100% Codeüberdeckung.
Exceptions:
Keine Exceptions abfangen! Nur welche werfen. Wenn etwas schief geht, muss der Benutzer der Klasse das merken.
-
Ok habe das lock problem und die Rückgabewerte wenn Key nich gefunden wurde gändert.
Ich muss navigieren.. der cyclebuffer wird ständig von einem Polling Thread mit next() durchlaufen, und holt sich bei jedem element ein schippe an daten. Wenn an einem elemen alle daten gelesen wurden löscht diese der Polling thread auch .. von ausen wird nur die "Add" funktion verwendent, d.h. während der Polling Thread fleissig am daten schieben ist, wird über die Addpunkt ein neues element in den polling zyklus kreis eingegliedert.
Ja ich habs bischer mit ner list und mit den Enumeratoren.. aber das stört mich.. weil bspw. bei einem Löschvorgang der enumerator "MoveNext()" ungültig ist. Und bei meinerm Cylce buffer gibts das problem auch net:)