AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren

Query in Thread abarbeiten (UniDac mit MsSQL)

Ein Thema von norwegen60 · begonnen am 23. Jan 2017 · letzter Beitrag vom 24. Jan 2017
Antwort Antwort
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
504 Beiträge
 
Delphi 12 Athens
 
#1

Query in Thread abarbeiten (UniDac mit MsSQL)

  Alt 23. Jan 2017, 14:46
Delphi-Version: 10 Seattle
Hallo,

seit Monaten will ich mich an dieses Thema wagen. Ich habe versucht unterschiedlichste Informationen zu berücksichtigen und habe es auch geschafft dass das Ganze läuft.

Bei dem gezeigten Beispiel geht es zunächst nur um Grundsätzliches. Natürlich wird der Thread später in eine eigene Unit verschoben und auch die Masterdaten entsprechend gehandelt.

Was soll das Programm machen:
  • Sobald man in den Masterdaten blättert, sollen die Detaildaten (ca. 20000) ausgelesen und in einem Chart und einer Virtualstringtree dargestellt werden.
  • Blättert man schnell durch die Daten soll der laufende Thread beendet werden bzw. für die Überblätterten Daten gar nicht erst beginnen
  • Natürlich sollen später auch die Masterdaten weg von der Oberfläche aber zum Testen tut es so.
  • Und auch die Frage, ob man 20000 Werte in einer Tabelle darstellen will, überlasse ich später dem Anwender.
Das funktioniert mit dem gezeigten Code auch ohne Probleme. Das Anzeigen von 20000 Daten in TChart und VST benötigt je 0.1s. Das Ecxecute der Query dauert ca. 1.2s. Eigentlich schon schnell genug um alles zu belassen, aber wie gesagt geht es mir generell um das Thema Threads und DB.

Delphi-Quellcode:
unit fo_ShowData;
{ ******************************************************************************
fo_ShowData
Programm um folgende Dinge zu testen
  * Daten per Thread auslesen und anzeigen
--------------------------------------------------------------------------------
Historie
23.01.17, V1.0.1, GPA, Delphi XE10
  * Verwaltung der Daten in TList unter Verwendung von TMonitor.Enter/Exit
23.01.17, V1.0.0, GPA, Delphi XE10
  * Verwaltung der Daten in TThreadList unter Verwendung von LockList/UnlockList
****************************************************************************** }


interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, ActiveX,
  Generics.Collections,
  Generics.Defaults,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, VclTee.TeeGDIPlus,
  VclTee.Chart, VclTee.TeEngine, VclTee.Series, Vcl.StdCtrls, VclTee.TeeProcs,
  VclTee.DBChart, Vcl.Grids, Vcl.DBGrids, VirtualTrees, Vcl.ExtCtrls,
  Vcl.ComCtrls, Nav, MemDS, DBAccess, Uni, UniProvider, SQLServerUniProvider,
  VclTee.TeeXML, VclTee.TeeSeriesTextEd, VclTee.TeeURL, VclTee.TeeExcelSource;

type
  TForm2 = class(TForm)
    paTop: TPanel;
    paData: TPanel;
    StatusBar1: TStatusBar;
    paManuell: TPanel;
    vstData: TVirtualStringTree;
    chForce: TChart;
    Series3: TLineSeries;
    Series4: TLineSeries;
    UniConnection1: TUniConnection;
    SQLServerUniProvider1: TSQLServerUniProvider;
    UniQuery1: TUniQuery;
    DataSource1: TDataSource;
    DBNavigatorSpec1: TDBNavigatorSpec;
    laReport: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure DataSource1DataChange(Sender: TObject; Field: TField);
    procedure vstDataGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure vstDataGetNodeDataSize(Sender: TBaseVirtualTree;
      var NodeDataSize: Integer);
    procedure vstDataInitNode(Sender: TBaseVirtualTree;
      ParentNode, Node: PVirtualNode;
      var InitialStates: TVirtualNodeInitStates);
  private
    { Private-Deklarationen }
    procedure CreateThrReadDb(iID: Integer);
  public
    { Public-Deklarationen }
    procedure SyncReady(bValue: Boolean);
  end;

  TThreadBoolean = procedure(bValue: Boolean) of Object;

  PValues = ^TValues;

  TValues = class
    dtTime: TDateTime;
    rForce,
    rFtarget,
    rTemperatur,
    rHumidity: Real;
  End;

  TListData = class(TList<TValues>)
  end;

  TThrReadDb = class(TThread)
  protected
    procedure Execute; override;
  private
    _Ready: Boolean;
    _ReadyEvent: TThreadBoolean;
    dbcon: TUniConnection;
    dbProvider: TSQLServerUniProvider;
    dbQuery: TUniQuery;
    procedure syncReadyEvent;
  public
    property ReadyEvent: TThreadBoolean read _ReadyEvent write _ReadyEvent;
  end;

var
  Form2: TForm2;
  lstData: TListData;
  thrReadDb: TThrReadDb;
  bThrReadDbIsRunning: Boolean;

implementation

{$R *.dfm}

procedure TForm2.DataSource1DataChange(Sender: TObject; Field: TField);
// Bei jedem Blättervorgang in den Masterdaten Thread zum Auslesen der
// Detaildaten erzeugen
begin
  laReport.Caption := format('%s [%d]', [UniQuery1.FieldByName('Nr').AsString,
    UniQuery1.FieldByName('ID').AsInteger]);

  CreateThrReadDb(UniQuery1.FieldByName('ID').AsInteger)
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  lstData.Free;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  lstData := TListData.Create;
  UniQuery1.Open;
end;

procedure TForm2.CreateThrReadDb(iID: Integer);
// Thread zum Auslesen der Daten aus DB erzeugen
begin
  // Falls noch ein Thread zum Laden von Daten läuft diesen abbrechen
  if (bThrReadDbIsRunning) then
  begin
   // thrReadDb.dbQuery.BreakExec; // Führt zu Fehlermeldung "operation was canceled"
    thrReadDb.Terminate;
    bThrReadDbIsRunning := false
  end;

  lstData.Clear;

  bThrReadDbIsRunning := TRUE;
  thrReadDb := TThrReadDb.Create(TRUE);
  thrReadDb.ReadyEvent := Form2.SyncReady;

  { Create suspended--secondProcess does not run yet. }
  thrReadDb.FreeOnTerminate := TRUE;
  { You do not need to clean up after termination. }
  thrReadDb.Priority := tpLower; // Set the priority to lower than normal.

  if not assigned(thrReadDb.dbQuery) then
  begin
    thrReadDb.dbcon := TUniConnection.Create(NIL);
    thrReadDb.dbcon.ProviderName := TSQLServerUniProvider.Create(NIL).Name;
    thrReadDb.dbcon.ConnectString :=
      'Provider Name=SQL Server;Data Source=PsServer;Initial Catalog=ADatabase;Port=0;User ID=sa;Password=THREAD';

    thrReadDb.dbQuery := TUniQuery.Create(NIL);
    thrReadDb.dbQuery.Connection := thrReadDb.dbcon;
  end;

  if thrReadDb.dbQuery.Active then
    thrReadDb.dbQuery.Close;
  thrReadDb.dbQuery.SQL.Text :=
    format('select * from Cal_force where CalReportID = %d order by ID', [iID]);

  thrReadDb.Resume; { thread starten }
end;

procedure TForm2.SyncReady(bValue: Boolean);
// Daten der kompletten Liste VST und TChart zuweisen
var
  i: Integer;
  Values: TValues;

begin
  vstData.NodeDataSize := SizeOf(TValues);
  vstData.BeginUpdate;
  vstData.Clear;
  System.TMonitor.Enter(lstData);
  try
    vstData.RootNodeCount := lstData.Count;
  finally
    System.TMonitor.Exit(lstData);
  end;
  vstData.EndUpdate;

  Series3.Clear;
  Series4.Clear;
  System.TMonitor.Enter(lstData);
  try
    Values := TValues.Create;
    for i := 0 to lstData.Count - 1 do
    begin
      Values := lstData.Items[i];
      Series3.AddXY(Values.dtTime, Values.rForce);
      Series4.AddXY(Values.dtTime, Values.rFtarget);
    end;
  finally
    System.TMonitor.Exit(lstData);
  end;
end;

procedure TForm2.vstDataGetNodeDataSize(Sender: TBaseVirtualTree;
  var NodeDataSize: Integer);
begin
  NodeDataSize := SizeOf(TValues);
end;

procedure TForm2.vstDataGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
// Daten in Zellen schreiben
var
  Data: PValues;

begin
  Data := Sender.GetNodeData(Node);
  case Column of
    0: CellText := FormatDateTime('yyyy-mm-dd hh.mm.ss.zzz', Data^.dtTime);
    1: CellText := format('%7.2f', [Data^.rForce]);
    2: CellText := format('%7.2f', [Data^.rFtarget]);
  end;
end;

procedure TForm2.vstDataInitNode(Sender: TBaseVirtualTree;
  ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
// Den Knoten in VST die Daten von lstData zuweisen
var
  Data: PValues;
begin
  Data := Sender.GetNodeData(Node);

  begin
    System.TMonitor.Enter(lstData);
    try
      Data^ := lstData[Node^.Index];
    finally
      System.TMonitor.Exit(lstData);
    end;
  end;
end;

{ ======================== TThrReadDB ======================================== }

procedure TThrReadDb.Execute;
// Zuerst Query öffnen und dann alle Daten in lstData ablegen
var
  Values: TValues;
  iT, iT1 : LargeInt;

begin
  inherited;

  bThrReadDbIsRunning := true;
  CoInitialize(nil);
  try
    thrReadDb.dbQuery.Open;

    while not thrReadDb.Terminated and not thrReadDb.dbQuery.eof do
    begin
      Values := TValues.Create;
      Values.dtTime := thrReadDb.dbQuery.FieldByName('ErstDat').Value;
      Values.rForce := thrReadDb.dbQuery.FieldByName('Fact').Value;
      Values.rFtarget := thrReadDb.dbQuery.FieldByName('Ftarget').Value;

      System.TMonitor.Enter(lstData); // Liste gegen Zweitzugriff sperren
      try
        lstData.Add(Values);
      finally
        System.TMonitor.Exit(lstData); // Sperrung der Liste wieder aufheben
      end;

      thrReadDb.dbQuery.Next;
    end;

    if not thrReadDb.Terminated then
    begin
      _Ready := true;
      Synchronize(syncReadyEvent);
    end;
  finally
    CoUnInitialize;
    bThrReadDbIsRunning := false;
  end;
end;

procedure TThrReadDb.syncReadyEvent;
// Formular benachrichtigen, dass Daten komplett in lstData vorliegen
begin
  if assigned(_ReadyEvent) then
    _ReadyEvent(_Ready);
end;

end.
Jetzt meine Fragen:
  1. Wo erzeuge ich Speicherlecks
  2. Wo ist der Thread nicht sauber gekapselt
  3. Ist beim Abrufuf von vstData.RootNodeCount := lstData.Count; ein locken der Liste notwendig?
  4. Sieht jemand eine Möglichkeit, auch eine laufende Queryabfrage abzubrechen. Der von UniDac ermöglichte Aufruf von BreakExec führt beim nachfolgenden neu erzeugten Thread zur Fehlermeldung "operation was canceled"
  5. Wie kann ich z.B. eine 150ms Verzögerung einbauen, damit beim schnellen Blättern in den Daten der Thread (vor allem Query.Execute) nicht gestartet wird
Wie gesagt, geht es mir ums Verständnis und ich wäre deshalb froh, wenn jemand die Fehler aufzeigen könnte und nicht alles über den Haufen schmeisst. Ich hoffe doch, dass der Ansatz nicht ganz so verkehrt ist.

Danke
Gerd

Geändert von norwegen60 (23. Jan 2017 um 14:49 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

Registriert seit: 24. Okt 2006
Ort: Seifhennersdorf / Sachsen
5.275 Beiträge
 
Delphi 12 Athens
 
#2

AW: Query in Thread abarbeiten (UniDac mit MsSQL)

  Alt 23. Jan 2017, 18:17
Hallöle...

Ich lehne mich mal weit aus dem Fenster ohne alles nachgelesen zu haben...

[Nur meine Meinung...über vieles läßt sich streiten ]
1. Hat das einen Sinn das verschiede Variablen den_ Unterstrich tragen statt wie "üblich" F?
2. Warum hat das Event für den Thread den Namen "TThreadBoolean" statt einem sprechenden Namen wie "TFinishLoadList" ... am Namen sollte man schon die Funktionsweise "erahnen"
3. Die Connection gehört in den Thread...
4. Das gehört nicht in den Thread thrReadDb.ReadyEvent := Form2.SyncReady; Den Eventhander in der Form deklarieren und verbinden.
5. TListData = class(TList<TValues>) ... erzeugt dir Speicherlecks mit Values := TValues.Create; besser http://docwiki.embarcadero.com/Libra...ectList.Create
6. Keine Passworte hardcodiert im Connectionstring
7. thrReadDb.Resume; ... nicht mehr verwenden sondern START
8. thrReadDb.FreeOnTerminate := TRUE; habe ich fast überlesen zwischen den Kommentaren.
9. thrReadDb.dbcon.ProviderName := TSQLServerUniProvider.Create(NIL).Name; ... einfach zuordnen statt von neuer Instanz. (imho legt die UniDAC automatisch intern eine Instanz an) besser: FConnection.ProviderName := 'Interbase';
10. Aufteilung in separate Units. Die Logik (Thread) hat nichts in der Form Unit zu suchen.
11. WICHTG: Der Code ist nicht konsistent. Mal "true" mal "TRUE". Benutze lieber einen Codeformatter...


Geändert von haentschman (24. Jan 2017 um 07:44 Uhr)
  Mit Zitat antworten Zitat
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
504 Beiträge
 
Delphi 12 Athens
 
#3

AW: Query in Thread abarbeiten (UniDac mit MsSQL)

  Alt 23. Jan 2017, 22:42
Hallo,

vielen Danke fürs Feedback. Ich weiß dass es mühsam ist, den ganzen Code anzuschauen. Wenn ich aber 10 Threads mit unterschiedlichen Fragen öffne wirds wohl auch nicht einfacher. Deshalb habe ich mal alles in einen Topf geworfen (und auch in eine Unit )

1. Hat das eine Sinn das verschiede Variablen den_ Unterstrich tragen statt wie "üblich" F?
Habe ich geändert. Kam aus einem der ersten Beispiele und ich dachte das macht man bei Threads vieleicht so
2. Warum hat das Event für den Thread den Namen "TThreadBoolean" statt einem sprechenden Namen wie "TFinishLoadList" ... am Namen sollte man schon die Funktionsweise "erahnen"
Weil ich den für verschiedene Aufgaben verwende wo ich ein Boolean übergebe
4. Das gehört nicht in den Thread thrReadDb.ReadyEvent := Form2.SyncReady; Den Eventhander in der Form deklarieren und verbinden.
Habe ich geändert als ich den Thread in eine eigen Unit verschoben habe und eine Rückverlinkung zum Form benötigt hätte
5. TListData = class(TList<TValues>) ... erzeugt dir Speicherlecks mit Values := TValues.Create; besser http://docwiki.embarcadero.com/Libra...ectList.Create
Habe ich mir angeschaut. Hintergrung für TListData = class(TList<TValues>) ist, dass ich die Liste dem Master-Record anhängen will. In etwas so:
Delphi-Quellcode:
  TCalReport = class
  private
    FNr : String;
    ...
    FValues: TListData
  public
    ...
  end;
6. Keine Passworte hardcodiert im Connectionstring
War nur für die Schnelle
7. thrReadDb.Resume; ... nicht mehr verwenden sondern START
Werde ich ändern
8. thrReadDb.FreeOnTerminate := TRUE; habe ich fast überlesen zwischen den Kommentaren.
Was meinst du damit?
9. thrReadDb.dbcon.ProviderName := TSQLServerUniProvider.Create(NIL).Name; ... einfach zuordnen statt von neuer Instanz. (imho legt die UniDAC automatisch intern eine Instanz an) besser: FConnection.ProviderName := 'Interbase';
Habe die DB-Komponenten bisher immer in einem Datamodul angelegt. Da muss ich wohl noch üben.
10. Aufteilung in separate Units. Die Logik (Thread) hat nichts in der Form Unit zu suchen.
Da war ich die letzten Stunden dran, denn das ist ja eines der Ziele der Übung. Trennung GUI - Logic - Daten
11. WICHTG: Der Code ist nicht konsistent. Mal "true" mal "TRUE". Benutze lieber einen Codeformatter...
Bin ich mir am angewöhnen.

Es ist nicht ganz einfach Quellen zu finden, auf die man sich verlassen kann. Auch die Delphi-Beispiele verwenden z.B. Resume http://docwiki.embarcadero.com/CodeE...dList_(Delphi)
Und im Moment habe ich noch meine liebe Mühe mit dem Thema "Thread"

Grüße
Gerd

Geändert von norwegen60 (23. Jan 2017 um 23:50 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

Registriert seit: 24. Okt 2006
Ort: Seifhennersdorf / Sachsen
5.275 Beiträge
 
Delphi 12 Athens
 
#4

AW: Query in Thread abarbeiten (UniDac mit MsSQL)

  Alt 24. Jan 2017, 08:07
Hallöle...
Zitat:
8. thrReadDb.FreeOnTerminate := TRUE; habe ich fast überlesen zwischen den Kommentaren.
Was meinst du damit?
Das ist ja in Ordnung so. Ich hätte nur das thrReadDb.FreeOnTerminate := TRUE; direkt nach dem Erzeugen plaziert und die Kommentare weggelassen. Am Anfang bin ich davon ausgegangen das der Tread nicht fregegeben wird.
Zitat:
Habe ich mir angeschaut. Hintergrung für TListData = class(TList<TValues>) ist, dass ich die Liste dem Master-Record anhängen will. In etwas so:
Auch wenn der Master Record freigeben wird, gilt das nicht für den Listeninhalt. Du legst eine Instanz rein Values := TValues.Create; und die sollte freigegeben werden. Der Master Record kümmert sich nur um die Listeninstanz. Den tangiert in keinster Weise der Inhalt der Liste...
Zitat:
2. Warum hat das Event für den Thread den Namen "TThreadBoolean" statt einem sprechenden Namen wie "TFinishLoadList" ... am Namen sollte man schon die Funktionsweise "erahnen"
Über Namen kann man streiten. Ist ja in Ordnung so. Was ich meinte... Lieber ein Event mehr deklariert als ein "Master" Event. Bei dem Beispiel mit der Liste kann man im besser genahmsten Event die Liste im Event mit übergeben. Da gibt es zig Varianten der Ausführung...

Geändert von haentschman (24. Jan 2017 um 08:15 Uhr)
  Mit Zitat antworten Zitat
Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:34 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