Thread für DB Zugriff
-
Hallo erstmal
Folgendes Problem:
Ich habe ein Komponente geschrieben die Werte im Intervall (also Block von 1000 Datensätzen) aus der Datenbank liest.
Nun wollte ich den nächsten Block schon einlesen während der zuerst ausgelesene Block noch bearbeitet wird.
Da dachte ich nimmt man einfach ein TThread - Objekt.
Ich weiß das ein in Ausführung befindliches Programm ein Prozess ist.
Also meinen Komponente auf irgendeiner Form.Ein Prozess besteht mindestens aus einem Thread.
Also währe meine Projekt ja schon ein Thread.Wenn ich jetzt einen weiteren Thread erzeuge in dessen Execute()-Funktion
der Datenbankzugriff stattfindet. Wie bekomme ich dann im Projekt mit, wann alle Daten geladen wurden ?Threads sind einfach nicht mein Sache.
Wenn eine Behauptung nicht stimmt, oder jemand ne gute Lösung hat oder mir helfen kann...
Vielen Dank schon mal
-
Hi,
klar is dein Programm ein thread.
das mit dem mitbekommen...ich weiss nicht wie deine bearbeitung der gelesen daten aussieht, aber vielleicht wäre es schlauer die bearbeitung eines gelesen blöcks in einen thread zu packen, wie auch immer musst du eben die threads auf "terminated" überprüfen, da gibt es aber sicher auch gute tutorials im netz
CU
-
Entschuldige das ich mich nicht so besonders ausgedrückt habe, ich werds nochmal probiern.
Mein Hauptprogramm enthält ein Label auf welches ich etwas zeichnen will.
Dieses Label übergebe ich dem Konstruktor des Threads welcher darauf etwas zeichnet.Mein Problem ist. Wie bekomme ich mit, das der Thread mit zeichnen fertig ist?
Damit ich mit dem HauptThread weiter zeichnen kann, beziehungsweise sich die Threads mit dem zeichnen abwechseln?Die Form sieht so aus
//** CPP __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Label1->Visible = false; DBThread = new TDBThread(true,Label1); DBThread->OnTerminate = ThreadOnTerminate; DBThread->Priority = tpNormal; DBThread->FreeOnTerminate = true; } void __fastcall TForm1::Button1Click(TObject *Sender) { DBThread->Resume(); // Play }
Und der Thread
//** CPP __fastcall TDBThread::TDBThread(bool CreateSuspended,TLabel *Label) : TThread(CreateSuspended) { ThreadLabel = Label; } //--------------------------------------------------------------------------- void __fastcall TDBThread::Execute() { PaintLabel(); } //--------------------------------------------------------------------------- void __fastcall TDBThread::PaintLabel() { ThreadLabel->Canvas->Brush->Color = clWhite; ThreadLabel->Canvas->Pen->Color = clBlack; ThreadLabel->Canvas->FillRect(TRect(1,1,ThreadLabel->Width,ThreadLabel->Height)); randomize(); for (int i =1; i < ThreadLabel->Width; i++) { Sleep(50); int pos; ThreadEdit->Text = i; ThreadLabel->Canvas->MoveTo(i-1,pos); if (random(2) == 1) { pos = random (ThreadLabel->Height); } ThreadLabel->Canvas->LineTo(i,pos); } }
-
Hi,
ich bin mir nicht so ganz sicher aber wenn der zweite thread warten muss bis der erste fertig ist usw. frag ich mich, ob du mit den threads überhaupt was sparst. trotzdem kannst du dir mal das beispiel unter examples/apps/threads in bcb4 und 5 anschauen. die führen einen "threadzähler" ein und sind damit informiert über die zahl der laufenden threads und wann sie alle fertig sind.
vielleicht hilft dir das ein wenig
CU
-
Vielen Dank
Das Beispiel bietet die ähnliche Funktionalität, die ich brauche.
Danke für den diWink, wäre ich selbst nicht drauf gekommen.dir noch nen schönen Tag
Machs gutThanks
-
Zu Deinem Thread läßst sich eine ganze Menge sagen, was aber den Rahmen meiner Antwort sprengen würde. Nur soviel:
1.)PaintLabel musst Du unbedingt mit Synchronize(PaintLabel) ausführen
2.) Dein Thread terminiert nach dem ersten Durchlauf. D.h., nach dem ersten Resume ist der Thread beendet.void __fastcall TDBThread::Execute()
{
while (!Terminated)
{Synchronize(PaintLabel);
Suspend();
}
}
So kannst Du den Thread beliebig oft aufrufen bis Du Terminate auf true setzt.Warum Du ein Sleep(50) in der Methode PaintLabel aufrufst, ist mir auch nicht ganz klar.
Sicherlich gibt es jede Menge Artikel über Threads im Internet. Unter Umständen kannst Du dort auch den Artikel aus der c´t 1991 Heft 6 "Parallele Programmierung" finden, der recht ausführlich in Deutsch auf dieses Thema eingehtGruß
Gerhard
-
Danke Gerhard
Mein Thread-Problem ist leider immer noch nicht gelöst, aber ich werd mir den Artikel in der CT mal anschauen.
Ich begreif noch nicht wie es möglich ist im Hauptprogramm ein
WaitForSingleObject-Aufruf zu starten der auf den erzeugten Thread wartet.Main startet Thread
Thread führt DB-Zugriff durch
Durch Benutzereingabe in Main terminiert Thread während DB-Zugriff
und ein neuer sollte gestartet werden.Aber daraus folgt beim Thread logischerweise der Fehler "Abruch während Datenbank Abfrage"
Und durch Synchronize hängt das Hauptprogramm auch während des DB-Zugriffs.
Wenn mir da noch jemand helfen könnte...
Vielen Dank Gerhard
und Sorry wegen der späten Antwort
-
Threadsynchronisation ist ein Komplexes gebiet. Das problem von dir scheint zu sein, dass du keine Kontrolle mehr über den Thread hast. Es wird also zeit, Events einzuführen. Entweder Methoden (Callbacks) aus dem Thread welche bei gewissen ereignissen auftreten, oder du beginnst mit dem Versenden von Windows-Messages.
-junix
-
So bis jetzt
Haupt Programm I I I I Benutzer eingabe I Start Thread--------------->Thread I I I I I DB-Abfrage--> I I . I I . neue I . Benutzer I . Eingabe I . I I . Terminate-------------> - . (und dann eigentlich neuen Thread aufmachen) I Fehler wegen DB
Wenn man jetzt im Thread einen Message schickt oder auch OnTerminate abfängt
wird ja aus der aktuellen Bearbeitung des Hauptprogramms gesprungen und das hilft mir auch nicht.Der Benutzer soll die Oberfläche weiter bedienen können und im Hintergrund werden Daten aus der DB geladen. Kommt der Benutzer nun an die Stelle wo er
eine Anforderung stellt, und die Werte sind noch nicht aus der DB zurück
(Also der Thread ist noch nicht beendet). Soll das Hauptprogramm einfrieren
und nach der Beendigung wieder auftauen.Also eigentlich so
Haupt Programm I I I I Benutzer eingabe I Start Thread--------------->Thread I I I I I DB-Abfrage--> I I . I I . neue I . Benutzer I . Eingabe I . I I . Wait for Thread I . I<--------- I<---------------------I Und weiter gehts
Vielen Dank junix für deinen Beitrag
aber vieleicht gibt es ja noch ne Andere Lösung als Messages zu verschickenMöre
-
Klar hab ich die. Sonst würd ja was nicht stimmen. (o; Benutze deine Private "NonBlockingWaitForSingleObject" in der du einen Timeout definierst. (z.B. 200ms?) Anschliessend prüfst du das Resultat auf WAIT_TIMEOUT, wenn true, läufst du durch eine Schleife in der du die MessageQueue abarbeiten lässt (z.B. TApplication::ProcessMessages)... Aber vorsicht, durch diese Art der Parallelität entstehen ganz neue probleme (z.B. kann der Benutzer dann wieder den Auslösenden Button drücken, der wiederum eine 2. Instanz deines Abholthreads ausführt, etc. pp.) die du dann wieder abfangen musst...
-junix
-
Hi,
Ein Thread macht nicht die Milch fett. Besser ist, wenn du die Query Asyncron ausführst.
nimm ne TADOQuery und setze:
1. Connection auf eine TADOConnection- Komponente.
Die Connection- Kommponente setzt du auf :
CursorLocation=clUseClient ; // clUseServer geht nicht, da die Query dann nicht asyncron ausgeführt werden kann
ConnectOptions =ConnectOptions << coAsyncConnect ; // sagt es ja schon..es muss eine TADOConnection- Komponete sein, da andere diese Funktionalitäten nicht unterstützen.
2. ctStatic=ctStatic // etwas zügiger
3. CursorLocation= clUseServer; // sonst funzt Asyncron- Fetching nicht.
4. LockType= ltReadOnly; // angeblich etwas schneller
5. Prepared =true; // macht was her...
6. ExecuteOptions =ExecuteOptions << eoAsyncExecute <<eoAsyncFetch << eoAsyncFetchNonBlockin; // um Asyncron zu fetchenreagiere auf
OnFetchComplete
wird aufgerufen, wenn die Query komplett geladen wurdeund
OnFetchProgress
wird aufgerufen, wenn ein Datensatz gefetcht wurd. Man kann hier als eine Progressbar mitlaufen lassen.Vorsicht: Wenn man komplexe Abfragen aufbaut, die
- SubQueries in Select oder From- Teilen enthalten
- StoredProc- Aufrufe im Select -Part ausführen
- From- Teile beinhalten , die aus UNION's oder anderen Mengenoperationen bestehenDiese Abfragen führen durch die Hintergrundaktivitäten des SQL- Servers zu Verzögerungen beim Auslösen des OnFetchTrogress- Ereignisses. Dieses lässt sich damit begründen, dass der Server die SubQueries und Procs erst auf dem Server Fetchen muss, damit die Abfrage konsistent bleibt. Die Verzögerung ist also syncron mit dem Zeitbedarf der SubQueries, UNIOS usw. in der Abfrage.
Joh, nu hast du die Query asyncron.
in OnFetchComplete weist du nun die Query einer DataSource zu, die an einer Visualiserungs- Komponete angeschlossen ist.Solch ein Aufwand lohn sich aber nur, wenn die Query Zeitkritisch ist.
Bei sehr komplexen Queries bau ich die asyncrone Query in ein Thread ein. In diesem Thread lasse ich ein ProgressFormular mitlaufen. So kann ich mehrere Queries gleichzeitig ausführen und gleichzeitig exportieren.
-
Man das wird ja immer komplexer. Also soll ich ne Funktion schreiben die auf den Thread wartet aber Benutzereingaben zulässt. Hört sich schon häftiger an.
Mal sehn ob ich das umsetzen kann.Bis jetzt hab ich nur mit MySQLDac gearbeitet und hab deshalb noch nicht so den Plan von den ADO-Komponenten. Also kurz ich scheitere am ConnectionString,
da ich ja keinen MS-SQL-Server habe, sondern nur mySQL auf einem anderen Host.
Wie soll da der Connectionstring aussehen?Vielen Dank für eure Vorschläge
ich bin schon wieder am ausprobierenTschau Möre
-
Möre schrieb:
Man das wird ja immer komplexer. Also soll ich ne Funktion schreiben die auf den Thread wartet aber Benutzereingaben zulässt. Hört sich schon häftiger an.
Ich sagte ja, Threadsynchronisation ist ein komplexes gebiet (o:
Aber es ist weniger Schlimm als es sich anhört. Schau einfach mal in der Hilfe unter TApplication::ProcessMessages, was diese Funktion schönes bewirkt.
@Andreas: Auch auf die Gefahr hin, dass ich mich irre, aber vermutlich wird die Asynchrone Query genau auch einen eigenen Thread starten um asynchron arbeiten zu können. oder sie verwurschtelt ebenfalls die MsgQueue mit ProcessMessages... hmmm
-junix
-
Hallo Möre,
ich glaube, Du machst es Dir ein wenig zu kompliziert. Falls ich Dein Anliegen falsch verstanden habe, bitte ich um Nachsicht. Also:
Du hast einen Thread für Deinen Datenbankzugriff erzeugt. Hierbei ist auf folgendes zu beachten. In Abhängigkeit der verwendeten Datenbank bzw. Treiber ist die Datenbankverbindung in der Execute-Methode herzustellen. Das solltest Du immer genau dann machen, wenn die Treiber nicht threadsaved sind. Bsp. Interbase/Firebird. Wenn Du es nicht genau weißt, dann lieber auf Nummer Sicher, also DB_Verbindung in der Execute-Methode:void __fastcall TThread_DB::Execute() { // Die Datenbankverbindung z.B. ptrDB im Thread-Context erzeugen // nicht im Konstruktor ! ptrDB = new DB_??(); // soll Deine DB-Verbindung sein ptrDB->Connect(); // Verbinden mit der DB wobei ptrDB ein Zeiger auf eine entsprechende Komponente // sein soll, while (!Terminated) { // jetzt läuft der Thread bis Terminated zum Bsp.vom Hauptprogramm auf true gesetzt wird if (ptrDB->Connected() && DB_Query()) // das kann die Methode sein für Deine DB-Abfrage { Synchronize(Label); // hier entweder Dein Label aktualisieren // oder mit SendNotifyMessage( hMainForm,WM_QUERY,QUERY_FNISHED,THREAD_DB_QUERY); // Dein Main Programm darüber informieren. Musst Du natürlich einen entsprechenden // Messagehandler im MAIN Programm implementieren } else { // entweder wieder über Dein Label den Benutzer über den Fehler informieren oder wie oben mit SendNotify(.... } Suspend(); // Hier wird der Thread angehalten und beim nächsten resume wieder mit DB_Query // fortgesetzt usw. usw. } ptrDB->Disconnect(); //DB-Verbindung beenden delete ptrDB ; // Verbindungsobjekt löschen } // end of method -----------------------------------------------------------
Den Thread vorzeitig zu terminieren ist normalerweise nicht nötig und zum Teil auch recht kompliziert, weil Du z.B. in der Methode DB_Query() an jeder in Frage kommenden Stelle das Flag Terminated abfragen musst. Bei den meisten Datenbanken ist das Abbrechen einer Query auch gar nicht möglich.(Ja, ja ich weiß IB 7 kann es , Oracle kann es)
So nun zu Deinem Hauptprogramm:Mit TThread_DB->Resume() startest Du Deine Query
Ob der Thread noch läuft, kannst Du jederzeit mit
if (TThread_DB->Suspended())
feststellen und z.B. ein Button für den Start einer Query disablen oder den Benutzer einen entsprechenden Hinweis geben.
Hat der Thread seine Arbeit getan (nicht Terminated) ist suspended auf true und Du kannst durch resume eine neue Abfrage starten.
Durch Synchronize wird der Zugriff des Main-Threads (oder einfacher gesagt, Deines stinknormalen Hauptprogrammes) auf die Komponenten unterbunden (damit eben nicht zwei Threads gleichzeitg auf Elemente zugreifen können) und dadurch scheint Dein Hauptprgramm zu hängen. Deshalb Synchronize wirklich nur zur Aktualisierung von Elementen verwenden.
Ich hoffe Dir damit ein wenig geholfen zu haben ohne Dich noch mehr zu verwirren.Gruß
Gerhard<edit>Bitte Code-Tags verwendet!</edit>
-
Danke für den Hinweis auf ProcessMessage.
Also das ist erstaunlich und war mir bisher auch noch nicht bekannt. Problem ist nur wie du es schon beschrieben hast das der Thread der lief dann einfach weiter geführt wird und somit ein Thread nach dem anderen erzeugt werden würde.Ich weiß das diese MySQLDac-Kompo, die ich verwende nicht Multithreading gestatet. So wollt ich nun zwischen jedem Zugriff auf die Datenbank prüfen ob der Nutzer eine Event ausgelöst hat um darauf zu reagieren. Also den alten Thread beenden und einen neuen Thread zu starten. Also wie in deinem Code-Abschnitt.
Aber warum sollte es egal sein ob man Threads terminiert oder nicht ??
Ein Thread belegt doch auch Ressourcen und wo ein Thread geöffnet wird sollte er ja auch wieder geschlossen werden.Hier mein Code bis jetzt
einfach ein FORM mit MEMO und BUTTONdie Unit1.cpp
#include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma link "mySQLDbTables" #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- __fastcall TForm1::~TForm1() { } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { DB = new TmySQLDatabase(NULL); DB->DatabaseName = "testdatenbank"; DB->Host = "192.168.2.103"; DB->UserName = ""; DB->UserPassword = ""; DB->KeepConnection = true; DB->HandleShared = true; // Mein Flag was so etwas wie ein Mutex / Critcal Section darstellt // und da VCL-Kompos synchronisiert werden bietet es sich einfach an Memo1->Tag = 0; threadRun = false; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { if (!threadRun) { if (Thread) { while (Memo1->Tag != 1 ) { Sleep(199); } delete Thread; Thread = 0; } //DB->Open(); Thread = new TDBThread(DB,Memo1); Thread->Priority = tpIdle; Memo1->Tag = 0; Thread->Resume(); // PLAY threadRun = true; } else { threadRun = false; Memo1->Tag = 1; // Wenn 1 dann weis der Thread bescheid es wurde // abgebrochen und versucht sich zu beenden } } //---------------------------------------------------------------------------
die thread.cpp
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "thread.h" #pragma package(smart_init) //--------------------------------------------------------------------------- // Wichtig: Methoden und Eigenschaften von Objekten der VCL können nur // in Methoden verwendet werden, die Synchronize aufrufen, z.B.: // // Synchronize(UpdateCaption); // // wobei UpdateCaption so aussehen könnte: // // void __fastcall TDBThread::UpdateCaption() // { // Form1->Caption = "In Thread aktualisiert"; // } //--------------------------------------------------------------------------- __fastcall TDBThread::TDBThread(TmySQLDatabase *DB,TMemo *Memo) : TThread(true) { FMemo = Memo; Query = new TmySQLQuery(NULL); Query->Database = DB; // FreeOnTerminate = True; } //--------------------------------------------------------------------------- void __fastcall TDBThread::Execute() { iCounter = 0; bAbort = false; while(!Terminated) { Synchronize(SyncExecute); iCounter++; if (bAbort) { break; } } Query->Close(); if (Query) { delete Query; Query = 0; } Suspend(); Terminate(); } //--------------------------------------------------------------------------- void __fastcall TDBThread::SyncExecute() { //---- Hier den Thread-Code plazieren---- Query->SQL->Clear(); AnsiString sSQL = "SELECT * FROM history "; sSQL = sSQL + " WHERE item_id < " + iCounter; Query->SQL->Add(sSQL); Query->Open(); FMemo->Lines->Add(iCounter); if (FMemo->Tag == 1) // Hauptprogramm sagt abbrechen { Query->Close(); bAbort = true; FMemo->Tag == 2; return; } }
Jetzt fragt ihr euch bestimmt warum macht er da in TForm1::Button1Click
delete Thread usw.
Das ist ein bisschen komisch wenn ich nur terminate ausführe, sehe ich dann wie es im "Debugfenster/Threads" immer mehr Threads werden, aber nie verschwindet einer?
Also die Button1Click ist noch ein bisschen buggy, geb ich zu.Wenn noch jemand eine bessere Lösung findet oder weis warum die Threads bei terminate trotzdem weiter existieren ??
Ich wäre sehr dankbar
Vielen Dank für eure AntwortenTschau Möre
-
Möre schrieb:
Problem ist nur wie du es schon beschrieben hast das der Thread der lief dann einfach weiter geführt wird und somit ein Thread nach dem anderen erzeugt werden würde.
Das musst du hald einfach abfangen...
-junix
-
Langsam Überblick ich es nicht mehr. Ich weiß das man mit,
if(WaitForSingleObject(hThread ,500) == WAIT_TIMEOUT)
erkennen kann wann ein Thread sich nicht in der angegebenen Zeit beendet hat.
Aber was mach ich damit.Ruf ich Terminate auf, geht das zwar schnell aber der Thread existiert noch im Debugfenster/Threads vom BCB.
Ruf ich delete Thread auf, verschwindet der Thread zwar aber das dauert auch wieder solange wie ein DB-Zugriff. Also nichts gekonnt.
*verzweifel*
Danke junix
-
Wieso denn den Thread abwürgen? Ich dachte du wolltest auf das Beenden der Abfrage warten?
-junix
-
Der User sorgt für ein Event.
Durch das Event wird ein Thread gestarted der Daten aus der DB auf einer Kompo darstellt.
Sorgt der User wieder für ein Event (wegen ungedult).
Wird die Bearbeitung des 1. Threads abgebrochen
und das ganze beginnt von Anfang an
Also neuer Thread mit neuen Daten aus der Datenbank....Die Probleme traten bis jetzt immer beim DB-Zugriff auf wenn in dem Moment der Thread beendet wurde.
Lösung war das die Main-Thread dem Child-Thread über eine gemeinsam genutzte Variable (Memo1->Tag) bescheid gesagt hat, Child-Thread beende dich.
Da Memo = VCL wird dies automatisch über Syncronice gelöst.Optimal wäre das nun noch wenn ich, darauf warten könnte bis sich der Thread beendet hat, und ihn nicht auf gut Glück einfach deleten würde.
Entschuldige das ich dir soviel Zeit stehle
Möre
-
Möre schrieb:
Sorgt der User wieder für ein Event (wegen ungedult).
Wird die Bearbeitung des 1. Threads abgebrochen
und das ganze beginnt von Anfang anWieso? Was ist falsch daran, einfach den user daran zu hindern den Event nochmals auszulösen während der erste Thread schon läuft? *amkopfkratz*
-junix