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



  • @rincewind

    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



  • @rincewind

    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 gut

    Thanks



  • 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 eingeht

    Gruß
    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



  • @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 verschicken

    Mö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 fetchen

    reagiere auf
    OnFetchComplete
    wird aufgerufen, wenn die Query komplett geladen wurde

    und
    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 bestehen

    Diese 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.



  • @junix

    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.

    @AndreasW

    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 ausprobieren

    Tschau 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>



  • @junix

    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.

    @Gerhard

    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 BUTTON

    die 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 Antworten

    Tschau 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



  • @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



  • @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 an

    Wieso? 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


Anmelden zum Antworten