Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

28.2 Threads mit Hilfe einer Wrapperklasse

28.2 Threads mit Hilfe einer Wrapperklasse

Wie Sie eben gesehen haben, muss man ziemlich viel machen, um einen Thread zu starten und ihn zu verwalten. Deswegen scheint es klüger, diese ganzen Mechanismen in eine Klasse zu packen, welche Sie im Anhang finden können. Nun gibt es aber zwei Vorgehensweisen. Die erste, und in anderen Sprachen die gängige Variante, ist eine Basisklasse zu bauen und jene anschließend abzuleiten um die eigene Threadfunktion als Methode einzubauen. Ein Vorteil der sich daraus ergibt ist, dass man auf die zusätzliche Datenstruktur verzichten kann, da man alle notwendigen Variablen als Attribute der Klasse deklarieren kann. Entsprechend muss man dann entweder Setter oder einen geeigneten Konstruktor bauen. Wie so eine abgeleitete Klasse aussehen könnte, sehen Sie im folgenden Quelltext.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
					
#include "Thread.h"

// Wrapperklasse für einen Thread
class CMyThread : public CThread {
	private:
		int m_iSart;
		int m_iEnd;
		int m_iID;

	protected:
		// Threadfunktion /////////////////////////////////////////////////////
		inline virtual DWORD Execute(void) {
			for (int iCount = this->m_iSart; iCount < this->m_iEnd && !this->IsTerminated; iCount++) {
				printf("Thread: %i - %i\n", this->m_iID, iCount);
				Sleep(10);
			} // end of for

			return 0;
		}

	public:
		// Benutzerdefinierter Konstruktor ////////////////////////////////////
		inline CMyThread(int iStart, int iEnd, int iID)
				: CThread() // Konstruktor der Basisklasse aufrufen
				, m_iSart(iStart)
				, m_iEnd(iEnd)
				, m_iID(iID)
		{} // CMyThread ///////////////////////////////////////////////////////
};
					

In den Zeilen 6 bis 8 habe ich jetzt alle Variablen aus der vorherigen Struktur als Attribute meiner Klasse definiert. In den Zeilen 12 bis 19 überschreibe ich jetzt die abstrakte Methode der Basisklasse. Jene wird dann durch den Thread ausgeführt. Genau genommen wird eine statische Methode der Basisklasse ausgeführt, welche diese nur aufruft, was auch die vereinfachte Funktionsschnittstelle erklärt. Jene darf aber auch wieder nicht modifiziert werden. Der Inhalt dieser Methode wurde auch eingedampft und enthält jetzt nur noch die reine Logik der Funktion, da wir keine Struktur mehr umständlich holen und umwandeln müssen. Des weiteren habe ich in Zeile 13 die Bedingung der Schleife dahingehend erweitert, dass sie Vorzeitig beendet werden kann. Somit ist es einfacher möglich, den Thread zu Terminieren.

In den Zeilen 23 bis 28 habe ich jetzt einen eigenen Konstruktor implementiert, welchem ich alle notwendigen Daten übergeben kann, welche ich später brauche. Gleich in Zeile 24 sieht man, dass mein Konstruktor explizit den Konstruktor der Basisklasse aufruft, allerdings ohne Parameter, da es für jene Defaultwerte gibt, welche ich auch so verwenden will. Fall Sie also meine Klasse benutzen und den Konstruktor überschreiben, müssen Sie auf jeden Fall den Konstruktor der Basisklasse aufrufen, da Ihre Klasse ansonsten überhaupt nichts macht. Dies liegt daran, das ich die ganze Thematik des Threaderzeugens und dessen Initialisierung, in den Konstruktor gepackt habe.

Als nächstes werde ich Ihnen zeigen, wie jetzt die "main" aussieht und Sie werden sofort feststellen, dass man auch hier bedeutend weniger machen und vor allem beachten muss.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
					
#include <stdio.h>
#include "MyThread.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	CMyThread*	pThread1	= new CMyThread(0, 10, 1);
	CMyThread*	pThread2	= new CMyThread(1000, 1010, 2);

	printf("START\n");

	pThread1->Resume();
	pThread2->Resume();

	// Warten, bis alle Threads fertig sind
	while (!pThread1->IsFinished() || !pThread2->IsFinished()) Sleep(1);

	delete pThread1;
	delete pThread2;
	
	printf("ENDE\n");

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

In Zeile 8 und 9 werden jetzt zwei Threadobjekte erzeugt, an welchen der tatsächliche Thread hängt, wobei jene pausiert erzeugt werden. Dies liegt daran, dass in meiner Threadklasse dieses Verhalten als Standard vorgegeben ist und ich im Konstruktor der abgeleiteten Klasse kein abweichendes Verhalten angegeben habe. Dadurch muss ich in Zeile 13 und 14 die Threads von Hand starten. Dies kann wie gesagt vermieden werden. Diese Herangehensweise macht aber, wenn auch nicht in diesem Beispiel, durchaus Sinn, denn wenn man keinen Konstruktor hat, welcher alle notwendigen Werte befüllt, sondern auf Set Methoden zurückgreift, wäre es suboptimal, wenn der Thread loslegt, bevor er alle Werte hat und selbst wenn der Konstruktor alles initialisiert, so muss zu mindestens der Aufruf des Basisklassenkonstruktors als letztes in der Initialisierungsliste stehen, da es sonst auch hier dazu kommt, dass der Thread schon loslegt, bevor er alle Werte hat. Neben der Startfunktion "Resume", gibt es auch eine Methode "Suspend", welche den Thread vorübergehend pausieren kann. Zu einem Späteren Zeitpunkt kann dann wieder "Resume" aufgerufen werden, um den Thread fortzusetzen. Beide Methode basieren auch auf den Win API Funktionen, nur dass ich noch zusätzliche Abfragen eingebaut habe, die verhindern, dass man z.B. einen pausierten Thread nochmal pausieren kann und so weiter.

Anschließend gebe ich jetzt den Threads Zeit ihre Arbeit zu verrichten, denn wenn ich sie gleich wieder löschen würde, hätte dies zur Folge, dass sie nichts oder nur wenig machen. Dies liegt daran, dass ich im Destruktor der Threadobjekte ein Terminieren veranlasse, falls dieser noch nicht zum Ende gekommen ist. Dies ist notwendig, weil ich ein Threadhandle erst dann freigeben kann, wenn die Threadfunktion zum Ende gekommen ist. Löscht man also gleich die Threadobjekte, kann wieder der Effekt auftreten, dass die Threadfunktion mittendrin beendet wird und eventuelle Entsperrungen nicht mehr vornehmen kann. Da man aber allgemein nicht weiß, wie lange ein Thread braucht (zumal dies auf verschiedenen Rechnern variieren kann), ist es sehr Unklug, einfach eine Zeit X zu warten. Deshalb bietet meine Klasse die Möglichkeit zu prüfen, ob der Thread fertig ist. Dies macht man, wie Sie sehen, mit der Methode "IsFinished". Alternativ bietet meine Threadklasse jetzt aber die Möglichkeit, ihn sanft zu Terminiren, also ihm nur Bescheid zu geben, dass er bei nächster Gelegenheit zum Ende kommen soll. Dies macht man mit dem Aufruf der Methode "SoftTerminate".

Die Ausgabe dieser Demo ist wieder eine ähnliche, jedoch bin ich einigen schwer zu verstehenden Quelltext losgeworden bzw. habe ihn gut verlagert und somit ist die eigentliche Funktionalität losgelöst von der Verwaltung und somit leichter nachzuvollziehen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012