Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi Datenbankabfragen mit Threads? (https://www.delphipraxis.net/112297-datenbankabfragen-mit-threads.html)

hronny 18. Apr 2008 10:05


Datenbankabfragen mit Threads?
 
Ich hab hier eine lokale Datenbank die ich leider nur mittels ADO Komponenten abfragen kann. Da diese Abfragen über mehrere Tabellen geht und fast 250000 Einträge hervorbringt, ist es bestimmt sicherer das ganze über Threads zu lösen. Problem ist im Moment das mittels TTimer eine Uhr läuft, diese aber während der Abfrage steht (und man denkt das Programm ist fest). Die Abfrage ist schon gut optimiert und dauert fast 15 Sekunden. Eine Aktion während der Abfrage ist unmöglich.

Aber geschnallt habe ich das mit den Threads noch nicht. Wo wird die Datenbankabfrage ausgeführt und wo läuft die Uhr auf meinem Formular? Es gibt unter den Jedis eine Klasse JvThreadTimer kann ich mit der so etwas anfangen?

Luckie 18. Apr 2008 10:13

Re: Datenbankabfragen mit Threads?
 
Threading Tutorial: http://delphitutorials.michael-puff.de

hronny 19. Apr 2008 11:09

Re: Datenbankabfragen mit Threads?
 
Das Tutorial ist schön geschrieben, leider versteh ich immernoch nicht wo was hingehört. Thread erstellen mit VCL ok, verstanden. Aber wo kommt jetzt meine Datenbankgeschichte hin, also öffnen und auslesen? Dachte in den Thread, aber da brauch ich theoretisch keine Schleife. Aber wenn die Datenbank im Thread geöffnet wäre, wie kriege ich dann die Informationen wieder zurück ins Haupt-Formularfeld? Vielleicht kann mir das mal ein wenig verdeutlichen (meine Vermutung steht in Klammern)
  • ADOConnection1 (Haupt)
  • ADOQuery (Thread)
  • Timer für Uhranzeige in der Statusbar (Haupt)

hronny 19. Apr 2008 13:02

Re: Datenbankabfragen mit Threads?
 
So nach langem suchen hab ich die Lösung gefunden :-) Auf einer Webseite habe ich das gefunden und es funktioniert! Also mein TTimer läuft im Hauptformular. Dieser Timer wird gestartet sobald ich einen Button drücke und die Abfrage startet. In der ADOQuery Komponente ist der Funktion "AfterOpen" hinterlegt das der Timer stopt. Geht super!
Delphi-Quellcode:
type
  TOpenQuery = Class(TThread)
  Public
    constructor Create(Q: TADOQuery;sSQL: String);
  protected
    tQry: TADOQuery;
    tSQL: String;
    procedure Execute; override;
  end;
.
.
.
{ TOpenQuery } 

constructor TOpenQuery.Create(Q: TADOQuery; sSQL: String);
begin
  inherited Create(False);
  FreeOnTerminate:=True;
  tQry:=Q;
  tSQL:=sSQL;
end;

procedure TOpenQuery.Execute;
begin
  inherited;
  Screen.Cursor:=crAppStart;
  with tQry do
  Begin
    SQL.Text:=tSQL;
    Open;
  End;
  Screen.Cursor:=crDefault;
end;

sirius 19. Apr 2008 14:27

Re: Datenbankabfragen mit Threads?
 
Das sieht in meinen Augen nicht gut aus. Ich würde, um eine klare Schnittstelle zu schaffen, die komplette Datenbankarbeit samt aller Komponenten in den Thread legen.

hronny 20. Apr 2008 11:25

Re: Datenbankabfragen mit Threads?
 
Und wie kann ich es dann machen? Da müsste quasi der Thread in einer Schleife sein und auf ein Signal warten?

Jelly 20. Apr 2008 12:16

Re: Datenbankabfragen mit Threads?
 
Wenn sich mehrere Queries ein und diesselbe Connection teilen, riskierst du massive Probleme. Um sicher zu gehen, würde ich auf jeden Fall in jedem Thread seine eigene Connection verpassen.

hronny 20. Apr 2008 12:51

Re: Datenbankabfragen mit Threads?
 
Die "dumme" SyBase Datenbank erlaubt vom Hersteller nur exclusiv Zugriff, also eine Connection gleichzeitig. Somit ist das nicht möglich noch eine Verbindung zu erstellen bzw gibt es dann einen Fehler.

Luckie 20. Apr 2008 12:58

Re: Datenbankabfragen mit Threads?
 
Zitat:

Zitat von hronny
Also mein TTimer läuft im Hauptformular.

Wozu der Timer?

Jelly 20. Apr 2008 13:08

Re: Datenbankabfragen mit Threads?
 
Zitat:

Zitat von hronny
Die "dumme" SyBase Datenbank erlaubt vom Hersteller nur exclusiv Zugriff.

Ich kenne Sybase nicht, aber diese Aussage kann nicht zutreffen, höchstens auf einer Express oder sonstigen kostenlosen Version von Sybase. Es ist ja gerade Sinn und Sache einer Datenbank, eben gerade mehrere Connections zuzulassen.

mkinzler 20. Apr 2008 13:11

Re: Datenbankabfragen mit Threads?
 
Sybase kann definitiv mit mehreren Connections umgehen.
BTW hat den gleichen Ursprung wie der MSSQL-Server

hronny 20. Apr 2008 13:14

Re: Datenbankabfragen mit Threads?
 
Damit sich irgendetwas während der Abfragen auf dem Formularfeld "bewegt". In dem Fall eine Uhr in der Statusleiste.

Es ist eine Embedded Variante die zusammen mit einer Software geliefert wird.

Bernhard Geyer 20. Apr 2008 16:42

Re: Datenbankabfragen mit Threads?
 
Solange obiger Quellcode keine Fehlermeldung verursacht vewendest du keine Threads, da:

1, Kein passendes Threading-Appartment für das COM-Subsystem angemeldet wird
2, Du keinerlei vorkehrungen hast die Connection (hier vor allem das COM-Interface) entsprechend für den Thread zu marshallen.

sirius 20. Apr 2008 17:55

Re: Datenbankabfragen mit Threads?
 
Zitat:

Zitat von hronny
Und wie kann ich es dann machen? Da müsste quasi der Thread in einer Schleife sein und auf ein Signal warten?

Einfach so runtergetippt (und nur eine Möglichkeit ohne Sicherheiten):
Delphi-Quellcode:
interface

uses Classes, AdoDB, Windows, Messages, ActiveX;


const DM_Base=WM_User;
      DM_Ask=DM_Base+1;
      DM_Next=DM_Base+2;

      DM_Last=DM_Next;

type TSqlEvent=procedure(answer:integer) of object;

type
  TOpenQuery = Class(TThread)
  Public
    constructor Create(AConnectString:WideString; //as is
                       AonTerminate:TNotifyEvent; //wird ausgelöst, wenn Thread beendet oder abbricht
                       AOnSqlEvent:TSqlEvent); //wird ausgelöst, wenn neues Datenbankergebnis
    procedure Terminate; override; //Thread beenden
    function AskSQL(aSqlText:string):boolean; //neues SQL_Statement
    function NextSQL:boolean; //=SQLQuery.Next
  protected
    FQry: TADOQuery;
    FCon: TADOConnection;
    FSql: String;
    FConnectString: WideString;
    FOnSQLEvent:TSQLEvent;
    FAnswer:Integer;
    procedure Execute; override;
    procedure DoSQLCommand(var msg:TMessage); message DM_Ask;
    procedure DoSQLNext(var msg:TMessage); message DM_Next;

    procedure DoSQLEvent;
  end;

implementation

constructor TOpenQuery.Create();
begin
  inherited Create(False);
  FreeOnTerminate:=True;
  FConnectString:=aConnectString;
  onTerminate:=AonTerminate;
  FOnSQLEvent:=AOnSQLEvent;
end;

procedure TOpenQuery.Execute;
var msg:TMSG;
begin
  coinitialize(nil);
  FCon:=TAdoConnection.Create(nil);
  Fqry:=TAdoQuery.Create(nil);
  try
    FCon.ConnectionString:=FconnectString;
    Fqry.Connection:=FCon;
    FCon.Open;
    while not terminated and getmessage(msg,0,0,0) do
    begin
      if (msg.message>=DM_Base)and
         (msg.message<=DM_Last) then
        dispatch(msg) //Als message an die Klasse verteilen
      else
        dispatchmessage(msg); //Windows verteilen lassen
    end;
  finally
    Fqry.Free;
    FCon.Free;
    CoUninitialize;
  end;
end;
procedure TOpenQuery.Terminate;
begin
  inherited;
  PostThreadMessage(self.ThreadID,WM_quit,0,0); //getmessage abbrechen
end;
procedure TOpenQuery.DoSQLCommand(var msg:TMessage);
var sqltext:PString;
begin
  sqlText:=PString(msg.LParam);
  Fqry.active:=false;
  Fqry.SQL.Text:=sqlText^;
  dispose(sqlText);
  Fqry.Open;
  if not Fqry.eof then
  begin
    FAnswer:=Fqry.FieldByName('zahl').AsInteger;
    synchronize(DoSQLEvent);
  end; //else Maintthread informieren
end;
procedure TOpenQuery.DoSQLNext(var msg:TMessage);
begin
  if (Fqry.Active)and (not Fqry.Eof) then
  begin
    Fqry.Next;
    FAnswer:=Fqry.FieldByName('zahl').AsInteger;
    synchronize(DoSQLEvent);
  end;// else an MainThread melden
end;

procedure TOpenQuery.DoSQLEvent;
begin
  if assigned(FonSQLEvent) then
    FOnSQLevent(FAnswer);
end;

function TOpenquery.AskSQL(ASqlText:string):boolean;
var SQLText:PString;
begin
  new(SQLText);
  SQLTExt^:=ASqlText;
  result:=PostThreadMessage(self.ThreadID,DM_Ask,0,integer(SQLText));
  if not result then
    dispose(SQLTExt);
end;
function TOpenQuery.NextSQL;
begin
  result:=PostThreadMessage(self.ThreadID,DM_Next,0,0);
end;
Das soll mal ein Gerüst darstellen. Kommuniziert wird hier über Messages, was aber innerhalb der Klasse bleibt. Ich habe Messages gewählt, weil ich mir nicht sicher bin, ob das die ADO-Komponenten benötigen.
Auf jeden Fall muss man hier noch einige Sicherheiten einbauen. Insbesondere, da bei Auftreten eines Fehlers, der Thread einfach beendet wird und dein Mainthread das maximal über die onTerminate-Routine mitbekommt.

sirius 20. Apr 2008 17:59

Re: Datenbankabfragen mit Threads?
 
Zitat:

Zitat von hronny
Und wie kann ich es dann machen? Da müsste quasi der Thread in einer Schleife sein und auf ein Signal warten?

Einfach so runtergetippt (und nur eine Möglichkeit ohne Sicherheiten):
Delphi-Quellcode:
uses Classes, AdoDB, Windows, Messages, ActiveX;


const DM_Base=WM_User;
      DM_Ask=DM_Base+1;
      DM_Next=DM_Base+2;

      DM_Last=DM_Next;

type TSqlEvent=procedure(answer:integer) of object;

type
  TOpenQuery = Class(TThread)
  Public
    constructor Create(AConnectString:WideString; //as is
                       AonTerminate:TNotifyEvent; //wird ausgelöst, wenn Thread beendet oder abbricht
                       AOnSqlEvent:TSqlEvent); //wird ausgelöst, wenn neues Datenbankergebnis
    procedure Terminate; reintroduce; //Thread beenden
    function AskSQL(aSqlText:string):boolean; //neues SQL_Statement
    function NextSQL:boolean; //=SQLQuery.Next
  protected
    FQry: TADOQuery;
    FCon: TADOConnection;
    FSql: String;
    FConnectString: WideString;
    FOnSQLEvent:TSQLEvent;
    FAnswer:Integer;
    procedure Execute; override;
    procedure DoSQLCommand(var msg:TMessage); message DM_Ask;
    procedure DoSQLNext(var msg:TMessage); message DM_Next;

    procedure DoSQLEvent;
  end;

implementation

constructor TOpenQuery.Create;
begin
  inherited Create(False);
  FreeOnTerminate:=True;
  FConnectString:=aConnectString;
  onTerminate:=AonTerminate;
  FOnSQLEvent:=AOnSQLEvent;
end;

procedure TOpenQuery.Execute;
var msg:TMSG;
    VCLmsg:TMessage;
begin
  coinitialize(nil);
  FCon:=TAdoConnection.Create(nil);
  Fqry:=TAdoQuery.Create(nil);
  try
    FCon.ConnectionString:=FconnectString;
    Fqry.Connection:=FCon;
    FCon.Open;
    while not terminated and getmessage(msg,0,0,0) do
    begin
      if (msg.message>=DM_Base)and
         (msg.message<=DM_Last) then
      begin
        VCLmsg.Msg:=msg.message;
        VCLmsg.LParam:=msg.lParam;
        VCLmsg.WParam:=msg.wParam;
        VCLmsg.Result:=0;
        dispatch(VCLmsg) //Als message an die Klasse verteilen
      end else
        dispatchmessage(msg); //Windows verteilen lassen
    end;
  finally
    Fqry.Free;
    FCon.Free;
    CoUninitialize;
  end;
end;
procedure TOpenQuery.Terminate;
begin
  inherited;
  PostThreadMessage(self.ThreadID,WM_quit,0,0); //getmessage abbrechen
end;
procedure TOpenQuery.DoSQLCommand(var msg:TMessage);
var sqltext:PString;
begin
  sqlText:=PString(msg.LParam);
  Fqry.active:=false;
  Fqry.SQL.Text:=sqlText^;
  dispose(sqlText);
  Fqry.Open;
  if not Fqry.eof then
  begin
    FAnswer:=Fqry.FieldByName('zahl').AsInteger;
    synchronize(DoSQLEvent);
  end; //else Maintthread informieren
end;
procedure TOpenQuery.DoSQLNext(var msg:TMessage);
begin
  if (Fqry.Active)and (not Fqry.Eof) then
  begin
    Fqry.Next;
    FAnswer:=Fqry.FieldByName('zahl').AsInteger;
    synchronize(DoSQLEvent);
  end;// else an MainThread melden
end;

procedure TOpenQuery.DoSQLEvent;
begin
  if assigned(FonSQLEvent) then
    FOnSQLevent(FAnswer);
end;

function TOpenquery.AskSQL(ASqlText:string):boolean;
var SQLText:PString;
begin
  new(SQLText);
  SQLTExt^:=ASqlText;
  result:=PostThreadMessage(self.ThreadID,DM_Ask,0,integer(SQLText));
  if not result then
    dispose(SQLTExt);
end;
function TOpenQuery.NextSQL;
begin
  result:=PostThreadMessage(self.ThreadID,DM_Next,0,0);
end;
Das soll mal ein Gerüst darstellen. Kommuniziert wird hier über Messages, was aber innerhalb der Klasse bleibt. Ich habe Messages gewählt, weil ich mir nicht sicher bin, ob das die ADO-Komponenten benötigen.
Auf jeden Fall muss man hier noch einige Sicherheiten einbauen. Insbesondere, da bei Auftreten eines Fehlers, der Thread einfach beendet wird und dein Mainthread das maximal über die onTerminate-Routine mitbekommt.

hronny 20. Apr 2008 22:03

Re: Datenbankabfragen mit Threads?
 
Danke erstmal für die aufwändige Antwort. Es erscheint mir sehr kompliziert, das muss ich in Ruhe durchprobieren. Dachte echt das geht einfacher. Ich brauch keine Multiabfragen, sondern einfach nur eine Datenbankabfrage nach der nächsten. Solange wie eine Abfrage läuft, soll sich in meinem Hauptformular irgendetwas bewegen (in dem Fall soll die Uhr weiter laufen). Nach der Abfrage möchte ich von meinem Hauptformular die Daten weiterverarbeiten. Vielleicht bekomme ich es mit dem oben genannten Ansatz hin, wobei ich in Threads noch absoluter Greenhorn bin.

Bernhard Geyer 20. Apr 2008 22:13

Re: Datenbankabfragen mit Threads?
 
ADO (oder wars nur der MS SQL Server über ADO) kann die Ergebnisdatenmenge (wenn sie z.B. in einem Grid benötigt wird) asynchron zurückliefern. Ob das auch der Provider von Sybase kann weis ich nicht.

alzaimar 21. Apr 2008 06:10

Re: Datenbankabfragen mit Threads?
 
Also wenn ich eine Abfrage hätte, die 15 Sekunden dauert, dann würde ich mal an dieser Abfrage rumfeilen. Optimiert ist das 100% *nicht*, denn mit dem Einsatz (redundanter) Zwischenergebnisse (z.B. Monatssummen o.ä.) würde man das in ein paar ms hinbekommen. Und falls die 15 Sek dafür benötigt werden, Hunderttausende von Datensätzen in die Applikation tu schaufeln, ist das auch nicht der richtige Weg.


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:27 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz