Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Delphi SQL Query in Thread wie Datenrückgabe realisieren (https://www.delphipraxis.net/190515-sql-query-thread-wie-datenrueckgabe-realisieren.html)

stOrM 11. Okt 2016 13:34

Datenbank: MySQL • Version: 5.7.11 • Zugriff über: UniDac

SQL Query in Thread wie Datenrückgabe realisieren
 
Moin,
ich denke das Thema ist schon immer wieder mal behandelt worden nur irgendwie nicht so wie ich es gerade brauche.
Es geht darum, dass ich im Moment versuche sämtliche Query's in einem Thread auszulagern, da mir die Programmoberfläche zu oft kurze Zeit einfriert. Abgesehen davon dachte ich mir, dass es ggf. nicht die schlechteste Wahl ist, die eigentlichen Daten irgendwo zwischen zu speichern und nicht unmittelbar an die Oberfläche zu hängen, so dass ich eventuell, sollte sich die Oberfläche mal ändern, ich nicht an bestimmte Komponenten gebunden bin.

Jetzt stellt sich aber die Frage wie gebe ich die Daten aus dem separaten Thread zurück an den Main Thread.
Ich beschreibe mal grob was ich gerade gemacht habe:

Komponenten die benutzt werden:

Unidac
DxGrid


1. Separaten Thread angelegt.
Dem Thread übergebe ich im Create als Parameter den SQL Query welcher ausgeführt werden soll.
Des weiteren werden hier alle benötigten Komponenten erstellt: UniConnection, UniProvider, UniQuery.

2. Im Execute des Threads wird das Uniquery mit Execute gestartet, welches dann mit den Daten die zurück kommen gefüllt wird, sofern der SQL Query erfolgreich ausgeführt werden konnte.

Tja und nun gehts los, wie übergebe ich die Daten jetzt an ein Dataset im Main Thread?

Genau hier hakt es, da ich ja nicht direkt auf Komponenten im Main Thread zugreifen darf (max. über Syncronize oder Messages), sollte ich jetzt z.B. eine Liste oder Collection erstellen, welche die Daten aufnimmt und diese dann an den Main Thread übergeben oder welche Möglichkeiten habe ich?

Die Idee war eigentlich folgende mit z.B. der TCollection.
Wenn diese die Felddefinitionen der Tabelle(Feldname, Type, Größe) sowie die Daten darin enthält, könnte ich z.B. diese Daten direkt in der Collection verwalten (Daten löschen, anlegen usw. und damit dann ein z.B. ein TSimleDataset füllen und an ein DBGid oder CXGrid übergeben) danach wird halt ein SQL Query an den SQL Server gesendet mit dem was gerade passiert ist (löschen, anlegen update usw.)

Macht man das so, oder gibt es da bessere oder schnellere Lösungen, wie macht Ihr das?

MyRealName 11. Okt 2016 15:10

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Wenn Du unidac nutzt, hast die die VirtualTable Komponente dabei, die ist mit nichts verbunden, sollte also Threadsafe sein.
Nach dem Threadstart aber noch vor dem Resume gibst du dem Thread object eine TVirtualTable mit und nach dem Query macht du ein

VirtualTable1.Assign(UniQuery1);

und dann sendest du deinem Mainthread eine message, dass die daten da sind und beendest den thread. Stell aber sicher, dass die virtualtable nicht genutzt wird, nur vor dem Thread.Resume ud wenn die Nachricht reinkam, dass die daten da sind.

Helge

Edit: da fällt mir ein, da Du ja dxgrid nutzt, schau dir mal den Servermode an, den gibt es auch für unidac, der lädt nur, was gerade an daten gebraucht wird um das grid zu füllen. Dadurch kannst du 1 mio datensätze haben, der lädt in 1 sekunde ohne probleme. auch beim scrollen wird da nur geladen, was gerade benötigt wird.

stOrM 11. Okt 2016 18:21

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von MyRealName (Beitrag 1350487)
Wenn Du unidac nutzt, hast die die VirtualTable Komponente dabei, die ist mit nichts verbunden, sollte also Threadsafe sein.
Nach dem Threadstart aber noch vor dem Resume gibst du dem Thread object eine TVirtualTable mit und nach dem Query macht du ein

VirtualTable1.Assign(UniQuery1);

und dann sendest du deinem Mainthread eine message, dass die daten da sind und beendest den thread. Stell aber sicher, dass die virtualtable nicht genutzt wird, nur vor dem Thread.Resume ud wenn die Nachricht reinkam, dass die daten da sind.

Helge

Edit: da fällt mir ein, da Du ja dxgrid nutzt, schau dir mal den Servermode an, den gibt es auch für unidac, der lädt nur, was gerade an daten gebraucht wird um das grid zu füllen. Dadurch kannst du 1 mio datensätze haben, der lädt in 1 sekunde ohne probleme. auch beim scrollen wird da nur geladen, was gerade benötigt wird.

Irgendwie hab ich gerade eine Denkblockade:

1. Ich soll dem Thread eine Virtualtable mitgeben, ok kann ich machen.
2. Dann soll ich später VirtualTable1.Assign(UniQuery1); ausführen. Woher soll Query1 jetzt gekommen sein, den gibt es nicht, also noch nicht.
Der Main Thread besitzt nur das Grid sonst nichts.

Alles andere wird ja im separaten Thread ausgeführt (also gibt es dort auch nur das Query.

Das mit dem Servermode klingt interessant, aber ist jetzt im Moment noch nicht mein Problem, behalte ich aber mal im Hinterkopf, zur Zeit gehts es nur um die Auslagerung der Querys in einem eigenen Thread und vor allem um die Rückgabe.

MyRealName 11. Okt 2016 18:39

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
OK, erstmal zum grundsatz : Ich nehme mal an, dass Du die Queries in einen Thread auslagern willst, weil sonst deine app zulange nicht reagiert, oder ?
Ich nehme mal an, dass es so ist ...

Der Servermode ist ja deswegen interessant, weil er halt die ergebnislisten so klein hält, dass du keinen thread brauchst, somit wäre dein Problem an der Stelle schon gelöst.

Aber gehen wir doch auf den Thread ein :

Komponenten die du brauchst sind :

TVirtualTable (VT) (Mainthread)
TUniConnection, TUniQuery (UniQuery1) (add. Thread)

Du erstellst den Thread suspended, gibst ihm die VT und startest den Thread. Der erzeugt die connection und die uniquery componenten, fährt die Abfrage und macht dann das assign zur VirtualTable, damit die VT die Daten hat. Dann schliesst das Query und die Verbindung und sendest deinem Mainthread die NAchricht, dass die Daten fertig sein und der zerstört den thread oder lässt den thread sich zerstören, aber nicht die VT, die kannst Du gleich als Dataset für das grid dann nutzen, aber erst der Datasource zuweisen, wenn der Thread fertig ist, sonst versicht das grid die schon zu lesen während du sie noch befüllst.

Verbesserung : Schau dass du vllt nur 100 datensätze erstmal liest um im Grid schon was anzuzeigen und im thread die anderen nachholst. IN firebird geht das mit

SELECT FIRST 100 (für die ersten 100)
SELECT FIRST 100 SKIP 100 (für die nächsten 100)

Bei MySQL kenn ich mich aber ned so aus

Ich hoffe, das hilft dir weiter

stOrM 11. Okt 2016 21:55

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von MyRealName (Beitrag 1350508)
OK, erstmal zum grundsatz : Ich nehme mal an, dass Du die Queries in einen Thread auslagern willst, weil sonst deine app zulange nicht reagiert, oder ?
Ich nehme mal an, dass es so ist ...

Der Servermode ist ja deswegen interessant, weil er halt die ergebnislisten so klein hält, dass du keinen thread brauchst, somit wäre dein Problem an der Stelle schon gelöst.

Aber gehen wir doch auf den Thread ein :

Komponenten die du brauchst sind :

TVirtualTable (VT) (Mainthread)
TUniConnection, TUniQuery (UniQuery1) (add. Thread)

Du erstellst den Thread suspended, gibst ihm die VT und startest den Thread. Der erzeugt die connection und die uniquery componenten, fährt die Abfrage und macht dann das assign zur VirtualTable, damit die VT die Daten hat. Dann schliesst das Query und die Verbindung und sendest deinem Mainthread die NAchricht, dass die Daten fertig sein und der zerstört den thread oder lässt den thread sich zerstören, aber nicht die VT, die kannst Du gleich als Dataset für das grid dann nutzen, aber erst der Datasource zuweisen, wenn der Thread fertig ist, sonst versicht das grid die schon zu lesen während du sie noch befüllst.

Verbesserung : Schau dass du vllt nur 100 datensätze erstmal liest um im Grid schon was anzuzeigen und im thread die anderen nachholst. IN firebird geht das mit

SELECT FIRST 100 (für die ersten 100)
SELECT FIRST 100 SKIP 100 (für die nächsten 100)

Bei MySQL kenn ich mich aber ned so aus

Ich hoffe, das hilft dir weiter

Deine Annahme ist völlig korrekt.
Ich bin im Moment nicht zuhause, aber ich werde das Morgen mal versuchen umzusetzen und melde mich dann mal mit deinem kompletten kleinen Projekt zurück, dass ist viel leicht sinnvoller als wenn ich das alles hypothetisch erklären bzw. hat vielleicht jemand anderes ein ähnliches Problem und lernt dann aus meinen Fehlern oder der Umsetzung wie man es machen kann.
Bis dahin erstmal vielen Dank für die nützlichen Tips!

stOrM 13. Okt 2016 12:17

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Ok also wie gesagt hab ich mich mal an den Thread gewagt und die Rückgabe in eine VirtualTable gelegt.

1. Problem, ich habe zur schnellen Überprüfung einfach ein DBGrid benutzt, manchmal jedoch nicht immer kommt es zu der Meldung: Canvas erlaubt kein zeichnen, gestoppt wird in VCL.Graphics Zeile 4197 Da weiss ich gerade auch nicht warum der Fehler auftritt.

2. Ein weiteres Problem zu dem es kommen kann und da wird es richtig wild, (Ich hab zum Test das Create, Start des Thread auf einen Button gelegt, wenn dieser aber zu schnell hintereinander geklickt wird haut es mir ziemlich viele Exceptions um die Ohren, angefangen von Blob Fehler bis hin zu (Gitternetz irgendwas, müsste ich noch mal genau nachsehen.)

Gut das kann ich schnell umgehen in dem ich einfach den Button so lange sperre wie der separate Thread läuft. In der späteren Hauptanwendung benutze ich ein PageControl, welches beim Seitenwechsel quasi den Thread starten soll um die GUI (Grid und weitere DBControls) mit den Werten zu füllen, da müsste ich mal sehen wie ich das unterbinde das die Seiten zu schnell gewechselt werden.

Dann schmeiss ich mal den Code hier rein und hoffe mir kann da jemand auf die Sprünge helfen bezüglich obiger Probleme (Ich geh einfach mal davon aus, mein Thread hat einen Designfehler oder die Hauptanwendung oder beides :shock: )

Die Anwendung:

Delphi-Quellcode:
unit fmView;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Vcl.StdCtrls, Vcl.ComCtrls,
  MemDS, VirtualTable, DBAccess, Uni, Vcl.Grids, Vcl.DBGrids,

  QueryThread;

type
  TView = class(TForm)
    DBGrid1: TDBGrid;
    UniDataSource1: TUniDataSource;
    VirtualTable1: TVirtualTable;
    StatusBar1: TStatusBar;
    MSGLog: TMemo;
    Label1: TLabel;
    btnStart: TButton;
    edtConStr: TEdit;
    edtSqlTxt: TEdit;
    QrPB: TProgressBar;
    procedure btnStartClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
    FSQLThrd: TSqlQueryThrd;
    procedure OnThreadStatusMsg(
      const ThreadStatusMsgPtr: PThreadStatusdMsg);

    procedure OnThreadQueryStarted(
      const ThreadQueryStartedMsgPtr: PThreadQueryStartMsg);

    procedure OnThreadQueryDone(
      const ThreadQueryDoneMsgPtr: PThreadQueryDoneMsg);

    procedure OnThreadQueryRecCount(
      const ThreadQueryRecCountMsgPtr: PThreadQueryRecCountMsg);
  public
    { Public-Deklarationen }
  protected
    procedure WndProc(var AMsg: TMessage); override;
  end;

var
  View: TView;

implementation

{$R *.dfm}

procedure TView.btnStartClick(Sender: TObject);
begin
  // I know this will leak at the moment!
  FSQLThrd := TSqlQueryThrd.Create(self.Handle, edtConStr.Text, edtSqlTxt.Text,
    VirtualTable1);
  try
    FSQLThrd.FreeOnTerminate := false;
    FSQLThrd.Start;
  except
  on E: Exception do
    MSGLog.Lines.Text := E.Message;
  end;
end;

procedure TView.FormDestroy(Sender: TObject);
begin
  if assigned(FSQLThrd) then
  begin
    FSQLThrd.Terminate;
    FSQLThrd.WaitFor;
    FSQLThrd.Free;
  end;
end;

procedure TView.OnThreadQueryStarted(
  const ThreadQueryStartedMsgPtr: PThreadQueryStartMsg);
begin
  case ThreadQueryStartedMsgPtr^.Running of
   true: QrPB.State := pbsNormal;
   false: QrPB.State := pbsPaused;
  end;
  Dispose(ThreadQueryStartedMsgPtr);
end;

procedure TView.OnThreadQueryDone(
  const ThreadQueryDoneMsgPtr: PThreadQueryDoneMsg);
begin
  case ThreadQueryDoneMsgPtr^.Done of
   true:
   begin
     QrPB.State := pbsPaused;
     try
       UniDataSource1.DataSet.DisableControls;
       UniDataSource1.DataSet := VirtualTable1;
       // exception
       // VCL.Graphics line 4197
       // EInvalidOperation: Canvas erlaubt kein zeichnen!
       DBGrid1.DataSource := UniDataSource1;
     finally
       UniDataSource1.DataSet.EnableControls;
     end;
   end;
   false: QrPB.State := pbsNormal;
  end;
   Dispose(ThreadQueryDoneMsgPtr);
end;

procedure TView.OnThreadStatusMsg(
  const ThreadStatusMsgPtr: PThreadStatusdMsg);
begin
  MSGLog.Lines.Add( string.Format('%s %s %s',
    [
     ThreadStatusMsgPtr^.ExecTime,
     ThreadStatusMsgPtr^.MessageStr,
     ThreadStatusMsgPtr^.SQLStr
    ]));
  Dispose(ThreadStatusMsgPtr);
end;

procedure TView.OnThreadQueryRecCount(
  const ThreadQueryRecCountMsgPtr: PThreadQueryRecCountMsg);
begin
  Statusbar1.Panels[0].Text := string.Format('RecCount: %s' ,
    [IntToStr(ThreadQueryRecCountMsgPtr^.ICount)]);
  Dispose(ThreadQueryRecCountMsgPtr);
end;

procedure TView.WndProc(var AMsg: TMessage);
begin
  with AMsg do
  begin
    case Msg of
      TS_THREAD_STATUS_AVAILABLE: OnThreadStatusMsg(
          PThreadStatusdMsg(WParam));

      TS_THREAD_QUERY_STARTED: OnThreadQueryStarted(
          PThreadQueryStartMsg(WParam));

      TS_THREAD_QUERY_DONE: OnThreadQueryDone(
          PThreadQueryDoneMsg(WParam));

      TS_THREAD_QUERY_COUNT: OnThreadQueryRecCount(
          PThreadQueryRecCountMsg(WParam));
    end;
  end;
  inherited;
end;

end.
Der Thread für die SQL Querys:

Delphi-Quellcode:
unit QueryThread;

interface

uses
  System.Classes,
  System.SysUtils,
  Winapi.Windows,
  Winapi.Messages,
  UniProvider,
  MySQLUniProvider,
  Data.DB,
  DBAccess,
  Uni,
  MemDS,
  MemData,
  VirtualTable;

// messages to be send to the main thread
const
  TS_THREAD_STATUS_AVAILABLE = WM_USER + 1;
  TS_THREAD_QUERY_STARTED = WM_USER +2;
  TS_THREAD_QUERY_DONE = WM_USER +3;
  TS_THREAD_QUERY_COUNT = WM_USER +4;

type
  TThreadStatusMsg = record
    MessageStr: string;
    SQLStr: string;
    ExecTime: String;
  end;
  PThreadStatusdMsg = ^TThreadStatusMsg;

  TThreadQueryStartMsg = record
    Running: Boolean;
  end;
  PThreadQueryStartMsg = ^TThreadQueryStartMsg;

  TThreadQueryDoneMsg = record
    Done: Boolean;
  end;
  PThreadQueryDoneMsg = ^TThreadQueryDoneMsg;

  TThreadQueryRecCountMsg = record
    ICount: Int64;
  end;
  PThreadQueryRecCountMsg = ^TThreadQueryRecCountMsg;

type
  TSqlQueryThrd = class(TThread)
  protected
    procedure Execute; override;
    procedure OnAfterFetchEvent(DataSet: TCustomDADataSet);
    procedure OnAfterOpen(DataSet: TDataSet);
  private
    FMainHandle: THandle;
    FSQLText: string;
    fStatusText: string;
    FUniDacConnection: TUniConnection;
    FUniDacSQLQuery: TUniQuery;
    FVirtualTable: TVirtualTable;

    ThreadStatusMsgPtr: PThreadStatusdMsg;
    ThreadQueryStartedMsgPtr: PThreadQueryStartMsg;
    ThreadQueryDoneMsgPtr: PThreadQueryDoneMsg;
    ThreadQueryRecCountMsgPtr: PThreadQueryRecCountMsg;

    function GetLongTime(aTime: TDatetime) : string;
  public
    constructor Create(aMainHandle: THandle; aConnectionStr: string;
      aSQL: string; aVTable: TVirtualTable); overload;
    destructor Destroy; override;

  end;

implementation

{ TSqlQueryThrd }

constructor TSqlQueryThrd.Create(aMainHandle: THandle;
  aConnectionStr, aSQL: string; aVTable: TVirtualTable);
begin
  inherited Create(True);
 
  // main thread handle
  FMainHandle := aMainHandle;

  FUniDacConnection := TUniConnection.Create(nil);
  FUniDacConnection.ProviderName := 'MySQL';
  FUniDacConnection.ConnectString := aConnectionStr;
  Assert(aConnectionStr <> '', 'Connection-String can not be empty!');

  // test if connection is successful otherwise goodbye
  try
    try
      FUniDacConnection.Connect;
    except
      terminate;
    end;
  finally
    // can be disconnected the unidac query established a connection by itself if needed
    FUniDacConnection.Disconnect;
  end;

  FSQLText := aSQL;
  Assert(FSQLText <> '', 'SQL-Text can not be empty!');

  FUniDacSQLQuery := TUniQuery.Create(nil);
  FUniDacSQLQuery.Connection := FUniDacConnection;
  FUniDacSQLQuery.FetchingAll;
  FUniDacSQLQuery.AfterFetch := OnAfterFetchEvent;
  FUniDacSQLQuery.AfterOpen := OnAfterOpen;
  FUniDacSQLQuery.SQL.Clear;
  FVirtualTable := aVTable;
end;

destructor TSqlQueryThrd.Destroy;
begin
  if FUniDacConnection.Connected then
    FUniDacConnection.Disconnect;
  FUniDacConnection.Free;
  FUniDacSQLQuery.Free;
  inherited Destroy;
end;

procedure TSqlQueryThrd.Execute;
begin
  NameThreadForDebugging('StormThread');

  FUniDacSQLQuery.SQL.Text := FSQLText;

  // send the main thread some messages...
  New(ThreadQueryStartedMsgPtr);
  ThreadQueryStartedMsgPtr^.Running := True;
  if not(PostMessage(FMainHandle, TS_THREAD_QUERY_STARTED,
    integer(ThreadQueryStartedMsgPtr), 0)) then
  begin
    Dispose(ThreadQueryStartedMsgPtr);
    terminate;
  end;

  New(ThreadStatusMsgPtr);
  ThreadStatusMsgPtr^.MessageStr := 'Status: Query started';
  ThreadStatusMsgPtr^.ExecTime := GetLongTime(Now);
  ThreadStatusMsgPtr^.SQLStr := '';
  if not(PostMessage(FMainHandle, TS_THREAD_STATUS_AVAILABLE,
    integer(ThreadStatusMsgPtr), 0)) then
  begin
    Dispose(ThreadStatusMsgPtr);
    terminate;
  end;

  try
    try
      FUniDacSQLQuery.Execute;

      // Must be set here otherwise the virtualtable in the main thread
      // does not contain any data!
      FVirtualTable.Assign(FUniDacSQLQuery);
    except
      FUniDacSQLQuery.Close;
      FUniDacConnection.Close;
      terminate;
    end;
  finally
    FUniDacSQLQuery.Close;
    FUniDacConnection.Disconnect;
    terminate;
  end;
end;

function TSqlQueryThrd.GetLongTime(aTime: TDatetime): string;
var
  formattedDate : string;
begin
  Result :='';
  try
    DateTimeToString(formattedDate, 'hh:nn:ss:zz', aTime);
  finally
    Result := FormattedDate;
  end;
end;

// special for unidac if that event is raised the query is done as written in the docu...
procedure TSqlQueryThrd.OnAfterFetchEvent(DataSet: TCustomDADataSet);
begin
  New(ThreadStatusMsgPtr);
  ThreadStatusMsgPtr^.MessageStr := 'Status: Query done';
  ThreadStatusMsgPtr^.ExecTime := GetLongTime(Now);
  ThreadStatusMsgPtr^.SQLStr := '// ' + FSQLText;
  if not(PostMessage(FMainHandle, TS_THREAD_STATUS_AVAILABLE,
    integer(ThreadStatusMsgPtr), 0)) then
  begin
    Dispose(ThreadStatusMsgPtr);
    terminate;
  end;

  New(ThreadQueryDoneMsgPtr);
  ThreadQueryDoneMsgPtr^.Done := True;
  if not(PostMessage(FMainHandle, TS_THREAD_QUERY_DONE,
    integer(ThreadQueryDoneMsgPtr), 0)) then
  begin
    Dispose(ThreadQueryDoneMsgPtr);
    terminate;
  end;
end;

// this event is raised when the record count is available
procedure TSqlQueryThrd.OnAfterOpen(DataSet: TDataSet);
begin
  New(ThreadQueryRecCountMsgPtr);
  ThreadQueryRecCountMsgPtr^.ICount := FUniDacSQLQuery.RecordCount;
  if not(PostMessage(FMainHandle, TS_THREAD_QUERY_COUNT,
    integer(ThreadQueryRecCountMsgPtr), 0)) then
  begin
    Dispose(ThreadQueryRecCountMsgPtr);
    terminate;
  end;
end;

end.

CarlAshnikov 13. Okt 2016 12:50

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Was mir beim ersten Drüberschauen aufgefallen ist:

Du benutzt Postmessage d.h. es wird nicht gewartet bis die Nachrichten verarbeitet sind. Kann es dadurch zu Problemen kommen weil der VCL-Thread und dein Query-Thread gleichzeitig auf Sachen zugreifen?

Du rufst häufig Terminate auf, wertest aber Terminated nicht aus. Terminate beendet den Thread nicht sofort!

stOrM 13. Okt 2016 13:00

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von CarlAshnikov (Beitrag 1350752)
Was mir beim ersten Drüberschauen aufgefallen ist:

Du benutzt Postmessage d.h. es wird nicht gewartet bis die Nachrichten verarbeitet sind. Kann es dadurch zu Problemen kommen weil der VCL-Thread und dein Query-Thread gleichzeitig auf Sachen zugreifen?

Du rufst häufig Terminate auf, wertest aber Terminated nicht aus. Terminate beendet den Thread nicht sofort!

Ja, und eventuell. Wegen dem Postmessage war mein Gedanke, dass nicht gewartet werden soll, da ich dachte das es so ggf. wieder zum Einfrieren der GUI kommen kann, denn dann hätte ich mir den seperaten Thread komplett sparen können oder Syncronize einsetzen können.

Ich kann wie gesagt mir das Problem im Moment selbst nicht erklären. Wegen dem Terminate geb ich Dir recht, dass ist so völliger Blödsinn.

P.s. Was mir noch einfällt, eigentlich sofern ich die Doku von Unidac richtig verstanden habe, greife ich erst dann auf Sachen zu bzw. weise diese zu wenn das Query fertig ist, also nicht vorher.
Lt. Doku ist dies der Fall wenn AfterFetch eintritt, siehe Thread.

nahpets 13. Okt 2016 14:28

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Verstehe von dem Thema nicht wirklich was, aber was mich verwundert:

Wenn der Thread fertig ist, weist Du Datasource die VirtualTable zu und dem Grid dann DataSource.

Das kann ich nachvollziehen, allerdings hätte ich erwartet, dass vor dem Start des Thread eben gerade diese Verbindung aufgehoben wird.

Ich würd' hier also erwarten, dass nach dem ersten Threadstarten für die restliche Laufzeit des Programmes die Verbindung VirtualTable->DataSource->Grid bestehen bleibt.
Allerdings hatte ich die bisherige Anforderung so verstanden, dass eben genau das nicht der Fall sein soll.

Dashier verstehe ich nicht:
Delphi-Quellcode:
 try
    try
      FUniDacSQLQuery.Execute;

      // Must be set here otherwise the virtualtable in the main thread
      // does not contain any data!
      FVirtualTable.Assign(FUniDacSQLQuery);
    except
      FUniDacSQLQuery.Close;
      FUniDacConnection.Close;
      terminate;
    end;
Execute nimmt man bei 'ner Query doch eigentlich, wenn man keine Ergebnismenge erwartet, also bei Insert, Update ...
Müsste es bei 'nem Select noch Open heißen.
Bei 'nem Execute ist doch eigentlich auch kein Close erforderlich.
Oder ist das hier in diesem Zusammenhang anders?

stOrM 13. Okt 2016 14:36

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von nahpets (Beitrag 1350767)
Verstehe von dem Thema nicht wirklich was, aber was mich verwundert:

Wenn der Thread fertig ist, weist Du Datasource die VirtualTable zu und dem Grid dann DataSource.

Das kann ich nachvollziehen, allerdings hätte ich erwartet, dass vor dem Start des Thread eben gerade diese Verbindung aufgehoben wird.

Ich würd' hier also erwarten, dass nach dem ersten Threadstarten für die restliche Laufzeit des Programmes die Verbindung VirtualTable->DataSource->Grid bestehen bleibt.
Allerdings hatte ich die bisherige Anforderung so verstanden, dass eben genau das nicht der Fall sein soll.

Dashier verstehe ich nicht:
Delphi-Quellcode:
 try
    try
      FUniDacSQLQuery.Execute;

      // Must be set here otherwise the virtualtable in the main thread
      // does not contain any data!
      FVirtualTable.Assign(FUniDacSQLQuery);
    except
      FUniDacSQLQuery.Close;
      FUniDacConnection.Close;
      terminate;
    end;
Execute nimmt man bei 'ner Query doch eigentlich, wenn man keine Ergebnismenge erwartet, also bei Insert, Update ...
Müsste es bei 'nem Select noch Open heißen.
Bei 'nem Execute ist doch eigentlich auch kein Close erforderlich.
Oder ist das hier in diesem Zusammenhang anders?

Ja da hast Du recht, dass mit dem Open bzw. Execute ist mir auch gerade aufgefallen, muss in dem Fall natürlich Open lauten. Danke für den Hinweis.

Zitat:

Das kann ich nachvollziehen, allerdings hätte ich erwartet, dass vor dem Start des Thread eben gerade diese Verbindung aufgehoben wird.
Das kann sollte ich vielleicht mal testen. Zumindest wäre das sinnvoll, Beim OnCreate des Formulars ist alles noch ungebunden, aber wenn der separate Thread gestartet wurde nicht mehr, ich ändere das mal ab.

nahpets 13. Okt 2016 14:48

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Was mich noch interessieren würde:

Muss man die VirtualTable vor der Datenübernahme nicht schließen und nach der Zuweisung auf DataSource nicht öffnen?

Sprich: Bekommt man bei
Delphi-Quellcode:
FVirtualTable.Assign(FUniDacSQLQuery);
'ne offene Datenmenge zurück?
Delphi-Quellcode:
procedure TView.btnStartClick(Sender: TObject);
begin
  UniDataSource1.DataSet.DisableControls;
  UniDataSource1.DataSet.Close;
  DBGrid1.DataSource := Nil;
  UniDataSource1.DataSet := Nil;
  // I know this will leak at the moment!
  FSQLThrd := TSqlQueryThrd.Create(self.Handle, edtConStr.Text, edtSqlTxt.Text, VirtualTable1);
  try
    FSQLThrd.FreeOnTerminate := false;
    FSQLThrd.Start;
  except
  on E: Exception do
    MSGLog.Lines.Text := E.Message;
  end;
end;

procedure TView.OnThreadQueryDone(
  const ThreadQueryDoneMsgPtr: PThreadQueryDoneMsg);
begin
  case ThreadQueryDoneMsgPtr^.Done of
   true:
   begin
     QrPB.State := pbsPaused;
     try
       UniDataSource1.DataSet.DisableControls;
       UniDataSource1.DataSet := VirtualTable1;
       DBGrid1.DataSource := UniDataSource1;
       UniDataSource1.DataSet.Open;
     finally
       // Das würd' ich nur machen, wenn's vorher keine Exception gab.
       UniDataSource1.DataSet.EnableControls;
     end;
   end;
   false: QrPB.State := pbsNormal;
  end;
   Dispose(ThreadQueryDoneMsgPtr);
end;

MyRealName 13. Okt 2016 14:52

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von stOrM (Beitrag 1350769)
Zitat:

Das kann ich nachvollziehen, allerdings hätte ich erwartet, dass vor dem Start des Thread eben gerade diese Verbindung aufgehoben wird.
Das kann sollte ich vielleicht mal testen. Zumindest wäre das sinnvoll, Beim OnCreate des Formulars ist alles noch ungebunden, aber wenn der separate Thread gestartet wurde nicht mehr, ich ändere das mal ab.

Das meinte ich mit

Zitat:

aber erst der Datasource zuweisen, wenn der Thread fertig ist, sonst versucht das grid die schon zu lesen während du sie noch befüllst.
Was wir immer machen ist auf den ButtonClick immer erstmal den Button abschalten (Enabled := False) und erst wieder anschalten, wenn wir fertig sind mit unserem Code.

Auch Dein RecordCount wird eventuell falsch sein, da UniQuery die Zahl anzeigt, die sie erstmal holen (default 25). Entweder musst Du QueryRecordCount property setzen (macht uniquery dann 2x das Query, einmal zum zählen und ein 2. Mal um die ersten Daten zu holen).
Ich empfehle einfach den RecordCount der VirtualTable nach dem Assign zu nutzen. Aber da beendest Du den Thread eh und du kannst im MainThread einfach den RecordCount der VT zu fragen.


@nahpets : ich glaube, es spielt keine Rolle, da VT eh das dataset schliessen muss um die Field Liste zu löschen, da die ja von DataSet übernommen wird. Und der VT sollte dann automatisch geöffnet werden. Aber das kann man ja überprüfen mit

Code:
If Not VT.State in dsBrowsing Then

stOrM 13. Okt 2016 14:58

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von MyRealName (Beitrag 1350774)
Zitat:

Zitat von stOrM (Beitrag 1350769)
Zitat:

Das kann ich nachvollziehen, allerdings hätte ich erwartet, dass vor dem Start des Thread eben gerade diese Verbindung aufgehoben wird.
Das kann sollte ich vielleicht mal testen. Zumindest wäre das sinnvoll, Beim OnCreate des Formulars ist alles noch ungebunden, aber wenn der separate Thread gestartet wurde nicht mehr, ich ändere das mal ab.

Das meinte ich mit

Zitat:

aber erst der Datasource zuweisen, wenn der Thread fertig ist, sonst versucht das grid die schon zu lesen während du sie noch befüllst.
Was wir immer machen ist auf den ButtonClick immer erstmal den Button abschalten (Enabled := False) und erst wieder anschalten, wenn wir fertig sind mit unserem Code.

Auch Dein RecordCount wird eventuell falsch sein, da UniQuery die Zahl anzeigt, die sie erstmal holen (default 25). Entweder musst Du QueryRecordCount property setzen (macht uniquery dann 2x das Query, einmal zum zählen und ein 2. Mal um die ersten Daten zu holen).
Ich empfehle einfach den RecordCount der VirtualTable nach dem Assign zu nutzen. Aber da beendest Du den Thread eh und du kannst im MainThread einfach den RecordCount der VT zu fragen.


@nahpets : ich glaube, es spielt keine Rolle, da VT eh das dataset schliessen muss um die Field Liste zu löschen, da die ja von DataSet übernommen wird. Und der VT sollte dann automatisch geöffnet werden. Aber das kann man ja überprüfen mit

Code:
If Not VT.State in dsBrowsing Then

Ich hab die Änderungen jetzt soweit eingebaut, ändert aber nichts an der Fehlermeldung mit dem Canvas.
Teilweise bekomme ich sogar noch folgendes: VCL.Grids stop in Zeile 888 GridIndex ausserhalb des gültigen Bereichs (mal was neues)

Was den RecordCount angeht denke ich nicht das dieser falsch ist, ich setze beim Query explizit FetchAll also soll lt. Doku alles an Daten geholt werden und nicht nur partiell.


Zitat:

Ich empfehle einfach den RecordCount der VirtualTable nach dem Assign zu nutzen.
Wenn das funktionieren würde hätte ich das getan, deshalb der Umweg über die Messages. Der RecordCount im Main bei der VT ist nämlich immer 0 bei mir mit dem Query gehts einwandfrei.

Mavarik 13. Okt 2016 15:06

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Warum nimmst Du die Windows MessageQue und nicht System.Messaging?
Dann kannst Du dir auch das ganze Pointer-Zeug sparen.

Warum nimmst Du eine TThread Klasse und nicht System.Threading?

Eine eigene Threadklasse nutze ich nur noch, wenn ich den Thread 1x Erzeuge und behalte...
Zum Beispiel um mit einen SetEvent(StartEvent) die Verarbeitung in Nano-Sekundenbereich beginnen zu lassen. Zum Beispiel,
wenn von der Eingangsqueue ein neuer SQL-Befehlt kommt.

Vorgehen:
- Ich gebe die SQL-Befehle in die ThreadSaveQueue. Bei Eingang wir der Event gefeuert und der Thread läuft sofort los.
- ich kann jederzeit neue Befehle in die Queue feuern und die werden nacheinander abgearbeitet.
- Der Thread erzeugt die Daten und packt diese in einen Ausgang-Queue. Damit ist der Thread frei für weitere Aufgaben.
- Da ich der Eingangsqueue eine anonyme Procedure mitgegeben habe, kann die Ausgangsqueue in einem 2. Thread nach und nach die Syncronize der anonymen Proceduren aufrufen, die die Daten in der UI darstellen.

Natürlich kann man das über die System.Threading auch machen! Ich nutze jedoch eine TThread-Klasse, da ich hierüber den MultiThread-Zugriff auf eine SQLite Datenbank serialisiere!

Wenn ich mit MultiThread/MultiConnectionfähigen Datenbanken arbeite, nutze ich natürlich den ThreadPool, um so viele Anfragen wie möglich gleichzeitig zu handeln und die Skalierbarkeit des Datenbankservers auszunutzen.

Mavarik

nahpets 13. Okt 2016 15:08

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von stOrM (Beitrag 1350776)
Ich hab die Änderungen jetzt soweit eingebaut, ändert aber nichts an der Fehlermeldung mit dem Canvas.
Teilweise bekomme ich sogar noch folgendes: VCL.Grids stop in Zeile 888 GridIndex ausserhalb des gültigen Bereichs (mal was neues)

Das erinnert mit an den ungültigen Gitterindex bei StringGrids.
Das passiert, wenn man auf 'ne Zelle, Spalte, Zeile zugfreift, die es nicht (mehr) gibt.

Kann in dem Zusammenhang mit DBGrids passieren, wenn die Spaltenzahl der aktuellen Datenmenge nicht mit der der vorherigen übereinstimmt, das Grid aber trotzdem versucht die Daten einzulesen.

Schau mal bitte, ob Dein Grid sowas in der Art kennt:
Delphi-Quellcode:
DBGrid.Fields.Clear;
DBGrid.Columns.Clear;
(Eigentlich braucht man sowas ja nicht, aber manchmal doch ;-))
Wenn ja, bau das nach dem Entfernen der Datasource noch ein oder vor der Zuweisung der Datasource, wenn Du die befüllte VT bekommst.

stOrM 13. Okt 2016 15:13

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von nahpets (Beitrag 1350780)
Zitat:

Zitat von stOrM (Beitrag 1350776)
Ich hab die Änderungen jetzt soweit eingebaut, ändert aber nichts an der Fehlermeldung mit dem Canvas.
Teilweise bekomme ich sogar noch folgendes: VCL.Grids stop in Zeile 888 GridIndex ausserhalb des gültigen Bereichs (mal was neues)

Das erinnert mit an den ungültigen Gitterindex bei StringGrids.
Das passiert, wenn man auf 'ne Zelle, Spalte, Zeile zugfreift, die es nicht (mehr) gibt.

Kann in dem Zusammenhang mit DBGrids passieren, wenn die Spaltenzahl der aktuellen Datenmenge nicht mit der der vorherigen übereinstimmt, das Grid aber trotzdem versucht die Daten einzulesen.

Schau mal bitte, ob Dein Grid sowas in der Art kennt:
Delphi-Quellcode:
DBGrid.Fields.Clear;
DBGrid.Columns.Clear;
(Eigentlich braucht man sowas ja nicht, aber manchmal doch ;-))
Wenn ja, bau das nach dem Entfernen der Datasource noch ein oder vor der Zuweisung der Datasource, wenn Du die befüllte VT bekommst.

Jep Columns.Clear gibt es bau ich mal ein, dass Canvas Problem bleibt aber wie gehabt komischerweise tritt es eher Random ein.

Also keine Ahnung was das Problem ist, ich hab jetzt im ButtonClick noch extra folgendes eingebaut zur Sicherheit, ändert weiterhin nichts an den Exceptions:

Code:
  DBGrid1.Columns.Clear;
  VirtualTable1.Open;
  VirtualTable1.Edit;
  VirtualTable1.ClearFields;
  VirtualTable1.Close;
  DBGrid1.DataSource := Nil;

MyRealName 13. Okt 2016 16:04

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich habe Dir mal schnell deins umgebaut, so dass es geht.
Die Zwischennachrichten abgeschaltet, wichtig ist ja erstmal, dass es überhaupt fehlerfrei läuft.

Nach dem Start des Threads wird nicht mehr auf die VT zugegriffen bis zum (neu implementierten) OnTerminate event. Die VT ist da noch geschlossen, von daher der Check aufs VirtualTable.Active.

Den Provider musst imm Thread nicht setzen, der ist im ConnectionString.
Musst ihn mal auf MySQL setzen wieder, ich habe nur Firebird installiert.


Ich habe den thread mehrfach ausgeführt ohne Fehler im Grid (nutze Delphi 10.1 Upd1), auch das Programm mehrfach, konte aber den Fehler nicht feststellen, den Du erwähntest.

Viel Spass :)
Helge

stOrM 13. Okt 2016 16:07

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von MyRealName (Beitrag 1350797)
Ich habe Dir mal schnell deins umgebaut, so dass es geht.
Die Zwischennachrichten abgeschaltet, wichtig ist ja erstmal, dass es überhaupt fehlerfrei läuft.

Nach dem Start des Threads wird nicht mehr auf die VT zugegriffen bis zum (neu implementierten) OnTerminate event. Die VT ist da noch geschlossen, von daher der Check aufs VirtualTable.Active.

Den Provider musst imm Thread nicht setzen, der ist im ConnectionString.
Musst ihn mal auf MySQL setzen wieder, ich habe nur Firebird installiert.


Ich habe den thread mehrfach ausgeführt ohne Fehler im Grid (nutze Delphi 10.1 Upd1), auch das Programm mehrfach, konte aber den Fehler nicht feststellen, den Du erwähntest.

Viel Spass :)
Helge

Ich check das mal fix aus, Mega Dankeschön für Deine Mühe!

MyRealName 13. Okt 2016 16:09

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Was ich nochmal erwähnen wollte zum Thema...

1. Es war so, wie ich sagt : nach dem SQL.Execute ist RecordCount bei 25, nach dem Assign auf der Zahl, was du wirklich an Register hast (Assign zieht alle register und dann kommt das natürlich hin). Der RecordCount der VT wird erst gesetzt, wenn sie offen ist (Active = True), sonst ist er 0.

Das im Thread ausführen bringt dir keinen Geschwindigkeitsvorteil, da das Query immer noch genauso langsam/schnell ist. Dazu müsstest Du die Daten stückeln, also in Blöcken anfordern. Wie ich vorher im Thread schonmal erwähnt hatte.

Helge

stOrM 13. Okt 2016 16:20

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Jetzt wirds ganz lustig :-D
Connection Name "Use Unicode" kennt er nicht. "CharacterSet" auch nicht. Wenn ich das raus nehme (ich denke mal liegt an der verschiedenen Version von Unidac die wir wohl haben) bekomm ich keine Daten :shock:
Macht aber erst mal nichts, ich werde das noch mal so umbauen mit meiner Version und dann mal schauen ob es dann geht, Ich denk mal Morgen bin ich damit soweit.

Alles klar funktioniert jetzt wunderbar. Nochmals vielen Dank für Deine Mühe!

MyRealName 13. Okt 2016 16:22

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Nimm einfach deinen ConenctionString, wie Du ihn vorher genutzt hattest. Wenn der den provider nicht hat, dann setz ihn halt im Thread wieder.

stOrM 13. Okt 2016 16:29

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von MyRealName (Beitrag 1350804)
Nimm einfach deinen ConenctionString, wie Du ihn vorher genutzt hattest. Wenn der den provider nicht hat, dann setz ihn halt im Thread wieder.

Passt schon ich hatte etwas in der Uses übersehen, klappt alles als hätte es nie Probleme geben :-D
Wenn ich mir genau ansehe was Du gemacht hast, denke ich kenne ich auch das Problem jetzt. Das Grid bei mir sah teilweise von den Spalten her etwas seltsam aus, manchmal waren diese komplett zusammen geschoben. So wie ich das sehe, war mein Zugriff in den Messages also sprich AfterFetch viel zu früh. Dein OnTerminate ist denke ich die Lösung gewesen.

MyRealName 13. Okt 2016 17:08

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Wichtig ist ja,d ass Du jetzt eine basis hast, auf der Du aufsetzen kannst.
Schau Dir trotzdem mal den SrverMode von DevExpress an, der löst dein Problem besser.

stOrM 13. Okt 2016 23:00

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von MyRealName (Beitrag 1350815)
Wichtig ist ja,d ass Du jetzt eine basis hast, auf der Du aufsetzen kannst.
Schau Dir trotzdem mal den SrverMode von DevExpress an, der löst dein Problem besser.

So siehts aus, dein Beispiel hat extrem geholfen.

@Mavarik
Das hört sich alles sehr interessant an und ist vermutlich ein sehr sauberer Weg, aber das was Du beschrieben hast sind so zu sagen Böhmische Dörfer für mich ich hab mit TThread schon so meinen Kampf, aber das versteh ich jetzt zumindest teilweise:-D

Mavarik 13. Okt 2016 23:17

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von stOrM (Beitrag 1350846)
Das hört sich alles sehr interessant an und ist vermutlich ein sehr sauberer Weg, aber das was Du beschrieben hast sind so zu sagen Böhmische Dörfer für mich ich hab mit TThread schon so meinen Kampf, aber das versteh ich jetzt zumindest teilweise:-D

Dann Frag doch - wo die Dörfer anfangen oder aufhören...

stOrM 14. Okt 2016 11:16

AW: SQL Query in Thread wie Datenrückgabe realisieren
 
Zitat:

Zitat von Mavarik (Beitrag 1350847)
Zitat:

Zitat von stOrM (Beitrag 1350846)
Das hört sich alles sehr interessant an und ist vermutlich ein sehr sauberer Weg, aber das was Du beschrieben hast sind so zu sagen Böhmische Dörfer für mich ich hab mit TThread schon so meinen Kampf, aber das versteh ich jetzt zumindest teilweise:-D

Dann Frag doch - wo die Dörfer anfangen oder aufhören...

Ich komme auf das Angebot gerne zurück, aber dazu muss ich mich erst mal in das Thema einlesen und mir mal so einige Beispiele ansehen um dann konkret Fragen stellen zu können. Das Thema ist komplettes Neuland für mich, ich hab es noch nie verwendet bisher. Vielleicht hab ich ja Glück und finde mal ein kleines Tutorial dazu, dann steig ich da mal tiefer ein.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:36 Uhr.

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