Nach längerer Blogging-Abstinenz gibt es nun endlich mal wieder einen neuen Eintrag. Außerdem bin ich voller Hoffnung, dass ich nun wieder öfters Zeit zum Bloggen finde ;-)
Heute möchte ich das Konzept des letzten Eintrags ("Objekte rechtzeitig erzeugen") ein wenig verbessern. In diesem Eintrag ging es darum, dass viele Algorithmen ganz schnell eine große Menge von Objekten eines bestimmten Typs für ihre Berechnungen brauchen. Um so schneller diese erzeugt werden, um so schneller läuft der Algorithmus. Optimal wäre es also, wenn die Objekte bereits existieren. Genau das macht die in diesem letzten Blog-Eintrag erstellte Klasse - sie erzeugt Objekte zu einem Zeitpunkt, zu dem die dafür benötigte Rechenleistung nicht ins Gewicht fällt.
Diese Klasse hat allerdings einen Nachteil: Alle benötigten Objekte werden auf einmal erzeugt. Das heißt, dass zu irgendeinem Zeitpunkt richtig viel Rechenpower verbraucht wird. Besser wäre es, wenn die Objekte alle schön nacheinander über einen größeren Zeitraum verteilt erzeugt werden. Während des normalen Programmablaufs einer WinForms-Anwendung würde es ja nicht stören, wenn einfach ein paar mal pro Sekunde einige wenige Objekte angelegt werden. Wird dann nach 5 Minuten der Rechen- (und Speicher- ) intensive Algorithmus gestartet, sind dann auch so die nötigen Objekte bereits vorhanden.
Die unten gezeigte ObjectCreator-Klasse implementiert solch ein Verhalten. Sie bedient sich hierzu der System.Windows.Timer-Klasse, welche in einem festen Intervall eine Methode von ObjectCreator aufruft. Zu diesen Zeitpunkten werden dann einige der verwalteten Objekte erstellt:
class ObjectCreator<TargetType> where TargetType : class, new()
{
// in diesem Stack werden die Objekte gespeichert
protected Stack<TargetType> mObjects = new Stack<TargetType>();
// der Timer, um regelmäßig Objekte zu erzeugen
protected Timer mTimer = null;
// wie viele Objekte sollen in jedem Intervall erzeugt werden?
protected int mNumObjPerTick = 1;
// wie viele Objekte sollen insgesamt erzeugt werden?
protected int mMaxObjs = 1000;
// Ermöglicht dem User, ein Maximum an verwalteten Objekten zu setzen / lesen
public int MaxObjects
{
get { return this.mMaxObjs; }
set { if (value > 0) this.mMaxObjs = value; }
}
// Anzahl aktuell verfügbarer Objekte
public int CountCurrentObjects
{
get { return this.mObjects.Count; }
}
// Konstruktor, initialisiert den Timer
public ObjectCreator()
{
mTimer = new Timer();
mTimer.Enabled = false;
mTimer.Tick += new EventHandler(mTimer_Tick);
}
// ein Objekt wird erstellt und auf dem Stack abgelegt
public void CreateObject()
{
TargetType tt = new TargetType();
mObjects.Push(tt);
}
// gibt ein Objekt zurück und entfernt es aus dem Stack
public TargetType GetObject()
{
if (mObjects.Count > 0)
return mObjects.Pop();
else return new TargetType();
}
// aktiviert das automatischen Erzeugen von
// NumObjObjekten alle MilliSecs Millisekunden
public void EnableAutoCreation(int MilliSecs, int NumObj)
{
if (MilliSecs > 0 && NumObj > 0 && this.mObjects.Count < this.mMaxObjs)
{
this.mTimer.Interval = MilliSecs;
this.mNumObjPerTick = NumObj;
this.mTimer.Enabled = true;
}
}
// deaktiviert die automatische Erzeugung wieder
public void DisableAutoCreation()
{
this.mTimer.Enabled = false;
}
// wird vom Timer alle MilliSecs Millisekunden aufgerufen
void mTimer_Tick(object sender, EventArgs e)
{
for (int i = 0; i < this.mNumObjPerTick; i++)
{
this.CreateObject();
}
}
}
Diese Implementierung verwendet einen Stack, um die erzeugten Objekte zwischenzuspeichern. Denkbar sind auch Ansätze mit anderen Container-Klassen, gerade auch ein Array kann sehr sinnvoll sein, da man sich bei geschickter Implementierung die Abfrage von "Count" in GetObject() sparen kann.
Die Klasse ist außerdem generisch, kann also zum frühzeitigen erzeugen aller Datentypen verwendet werden, die selbst ein Referenztyp sind und über einen Konstruktor verfügen. Ausgeschlossen sind hiervon die integralen Datentypen wie beispielsweise int, float, etc. Hierfür müßte man die Implementierung minimal abändern.
Grundsätzlich sollte man solche Klassen allerdings nur dann verwenden, wenn man wirklich viele Objekte verwendet, die auch noch eine gewisse Größe aufweisen. Bitmaps, Texturen, große Strings aber auch Klassen, die viele Membervariablen haben wären sinnvolle Beispiele. Bei der Anwendung sollte man auf jeden Fall testen, ob sich ein Geschwindigkeitsvorteil ergibt. Ist das Problem zu "gering", kann der Versuch durchaus nach hinten gehen, da der Verwaltungsaufwand nicht unerheblich ist. Bei großen Datenmengen kann man allerdings eine Geschwindigkeitssteigerung von Faktor 10 oder mehr verbuchen.