Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   query.open in Threads (https://www.delphipraxis.net/171009-query-open-threads.html)

mcinternet 15. Okt 2012 13:02

query.open in Threads
 
Hallo die Gemeinde,

habe hier ein kleines - oder auch großes Problem beim öffnen einer Oracle Query innerhalb eines Threads.
Die Query ist verknüpft mit einem cxdbgrid von Developer Express. Das Programm öffnet dynamisch mehrere Forms und die Fenster sollen während der Ausführung der Query natürlich nicht hängenbleiben - dafür das Threading, weil der Datenbestand auch entsprechend hoch ist. Wenn ich allerdings das query.open in den Thread lege, spinnt die Anzeige des Grids spradisch willkürlich. Hier mal der Beispielcode:
Delphi-Quellcode:
unit UNew;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, MemDS, DBAccess, Ora,
  Vcl.StdCtrls, cxGraphics, cxControls, cxLookAndFeels, cxLookAndFeelPainters,
  cxContainer, cxEdit, cxListBox, cxDBEdit, cxTextEdit, cxMaskEdit,
  cxDropDownEdit, cxLookupEdit, cxDBLookupEdit, cxDBLookupComboBox, cxStyles,
  cxCustomData, cxFilter, cxData, cxDataStorage, cxDBData,
  cxGridCustomTableView, cxGridTableView, cxGridDBTableView, cxGridLevel,
  cxClasses, cxGridCustomView, cxGrid;

type
//   TInstError = procedure(const Content: String) of object;
  TFrmNew = class(TForm)

    OraSession: TOraSession;
    fncsleep: TOraStoredProc;
    lbl_oraconnect: TLabel;
    lbl_orasleep: TLabel;
    lbl_3: TLabel;
    lbl_FormName: TLabel;
    lbl1: TLabel;
    lbl_FormTag: TLabel;
    OraSessCommon: TOraSession;
    Ds_searchMA: TOraDataSource;
    qry_searchMA: TOraQuery;
    qry_searchMAPER_PK: TIntegerField;
    qry_searchMAPER_STRORALOGIN: TStringField;
    qry_searchMAPER_STRFIRSTNAME: TStringField;
    qry_searchMAPER_STRLASTNAME: TStringField;
    qry_searchMAPER_BOLACTIVE: TIntegerField;
    qry_searchMAPER_STRPERNO: TStringField;
    cbb1: TcxLookupComboBox;
    btn_telechild: TButton;
    cxgrdbtblvwGrid1DBTableView1: TcxGridDBTableView;
    cxgrdlvlGrid1Level1: TcxGridLevel;
    cxgrd1: TcxGrid;
    cxgrdbclmnGrid1DBTableView1PER_PK: TcxGridDBColumn;
    cxgrdbclmnGrid1DBTableView1PER_STRORALOGIN: TcxGridDBColumn;
    cxgrdbclmnGrid1DBTableView1PER_STRFIRSTNAME: TcxGridDBColumn;
    cxgrdbclmnGrid1DBTableView1PER_STRLASTNAME: TcxGridDBColumn;
    cxgrdbclmnGrid1DBTableView1PER_BOLACTIVE: TcxGridDBColumn;
    cxgrdbclmnGrid1DBTableView1PER_STRPERNO: TcxGridDBColumn;
    mmo1: TMemo;
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);


    procedure FormCreate(Sender: TObject);
    function update : Boolean;
    procedure btn_telechildClick(Sender: TObject);
    procedure qry_searchMAAfterScroll(DataSet: TDataSet);

  private
    { Private-Deklarationen }

  public
    { Public-Deklarationen }
  protected
      procedure CreateParams(var Params: TCreateParams); override;

  end;

  TMyThread = class(TThread)
  private
  //  FInstError: TInstError;

    procedure execute; override;
    procedure doprogress;

  public
    //

  protected

    FormUsed : TFrmNew;


    //property InstError: TInstError read FInstError write FInstError;
  end;

var

  Fname : string;
  MThread : TMyThread;
  progressvar : SmallInt = 0;
  orasessioninprogress : Boolean = False;

implementation

{$R *.dfm}

uses UMain;

procedure TFrmNew.btn_telechildClick(Sender: TObject);
begin
  FrmMain.btn_telemainClick(sender);
end;

procedure TFrmNew.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;

function TFrmNew.update : Boolean;
begin
  case progressvar of
    1 : lbl_oraconnect.Caption := 'Connected';
    2 : lbl_orasleep.Caption := 'ausgeschlafen';
    3 : // im Moment nix;
  end;
  progressvar := 0; // rücksetzen
  Result := True;
end;


procedure TFrmNew.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  while orasessioninprogress do begin

    //
  end;
  qry_searchMA.Close;
  OraSessCommon.Disconnect;
//  MThread.Terminate;
//  FreeAndNil(MThread);

 FrmMain.killform(Self.Name, Self.Tag);
end;

procedure TFrmNew.FormCreate(Sender: TObject);
begin
  Self.Caption := Self.Name;
end;

procedure TFrmNew.FormShow(Sender: TObject);
begin

  lbl_FormName.Caption := self.Name;
  lbl_FormTag.Caption := inttostr(Self.Tag);
  MThread := TMyThread.Create(True);
  MThread.FormUsed := Self;
  MThread.FreeOnTerminate := false;
  MThread.Start;

end;

procedure TFrmNew.qry_searchMAAfterScroll(DataSet: TDataSet);
begin
  mmo1.Text:=Self.qry_searchMAPER_STRORALOGIN.AsString;
end;

procedure TMyThread.Execute;
begin
   orasessioninprogress := true;
   FormUsed.OraSessCommon.Connect;
   progressvar := 1;
   MThread.Synchronize(doprogress);
//   FormUsed.qry_searchMA.Open;
//   FormUsed.fncsleep.ParamByName('i_seconds').AsInteger:=4;
//   FormUsed.fncsleep.Execute;


   orasessioninprogress := false;
   progressvar := 3;
   MThread.Synchronize(doprogress);
   FormUsed.qry_searchMA.Open;
   progressvar := 2;
   MThread.Synchronize(doprogress);
end;

procedure TMyThread.doprogress;
begin
  FormUsed.update;
end;

 end.
Hier die Mainform von wo das Ganze gesteuert wird.:
Delphi-Quellcode:
unit UMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, UNew;

type
  PMultipleForm = ^TFrmNew;

  TFrmMain = class(TForm)
    btn_newform: TButton;
    box_frm: TListBox;
    lbl_main: TLabel;
    btn_telemain: TButton;
    procedure ShowHideClick(Sender: TObject);
    procedure btn_newformClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure box_frmClick(Sender: TObject);
    procedure btn_telemainClick(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }

    FormListe: TList;
    FormP: PMultipleForm;

    procedure newform(_name : string);
    procedure killform(_name : string; _tag : SmallInt);
  end;

var
  FrmMain: TFrmMain;
  FrmNames : array of string;

implementation

{$R *.dfm}


procedure TFrmMain.ShowHideClick(Sender: TObject);
begin
  FormP := FormListe[box_frm.ItemIndex];
  FormP^.Visible := not FormP^.Visible;

end;


procedure TFrmMain.box_frmClick(Sender: TObject);
begin
  FormP := FormListe[box_frm.ItemIndex];
  FormP^.Visible := not FormP^.Visible;

end;

procedure TFrmMain.btn_newformClick(Sender: TObject);
  var frm : TFrmNew;
begin



  GetMem(FormP, SizeOf(TFrmNew)); // --> reserviert an der Adresse von FormP den Speicher für die Form
  FormP^ := TFrmNew.Create(Self); // --> erzeugt die Form
  FormP^.Caption := FormP^.Caption + IntToStr(box_frm.Items.Count + 1);
  FormListe.Add(FormP); // --> fügt den Pointer zur Liste hinzu
  box_frm.Items.Add('Form' + IntToStr(box_frm.Items.Count + 1));
  Formp^.Tag := box_frm.Items.Count;
  Formp^.Name := 'Form'+inttostr(Formp^.Tag);
  Formp^.Show;

end;

procedure TFrmMain.btn_telemainClick(Sender: TObject);
  var idx : SmallInt;
    NFormP: PMultipleForm;
begin
  if btn_telemain.Caption = 'Anruf' then btn_telemain.Caption := 'Auflegen'
    else btn_telemain.Caption := 'Anruf';

  for idx := 0  to FormListe.Count-1 do
  begin
    NFormP := FormListe[idx];
    NFormP^.btn_telechild.Caption := btn_telemain.Caption;
  end;
end;

procedure TFrmMain.newform(_name : string);
begin
   setlength(FrmNames, length(FrmNames)+1); // Array um ein Element erhöhrn
   FrmNames[high(FrmNames)] := _name;
   box_frm.Items.Add(_name);
end;

procedure TFrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
var
  i: Integer;
begin
  for i := 0 to FormListe.Count-1 do
  begin
    FormP := FormListe[i];
    if Assigned(FormP^) then begin

    FormP^.Hide; // --> muss zuerst aufgerufen werden, da mit dem Aufruf von Free auch das OnHide-Ereignis aufgerufen wird.
    // da sich nach dem Aufruf von OnHide der Pointer verändert haben könnte würde ein EAccessViolent entstehen
    FormP := FormListe[i];
    FormP^.Free;
    FreeMem(FormP, SizeOf(TFrmNew)); // --> gibt den Speicher der für die Adresse reserviert wurde frei
    end;
  end;




  FreeAndNil(formliste);
end;

procedure TFrmMain.FormCreate(Sender: TObject);
begin
  FormListe := TList.Create;
end;

procedure TFrmMain.killform(_name : string; _tag : SmallInt);
  var idx : SmallInt;
begin
  for idx := 0 to box_frm.Count -1 do begin
    if _name = box_frm.Items[idx] then begin
      box_frm.Items.Delete(idx);
      FormP := Formliste[idx];
      FormP^.Hide;
      FormP^.Free;
      FreeMem(FormP, SizeOf(TFrmNew)); // --> gibt den Speicher der für die Adresse reserviert wurde frei
      FormListe.Delete(idx);
      Break;
    end;
  end;
end;


end.
Mal sehen, ob hier einer von Euch Spezis was findet :oops:

Gruss

Mc

mjustin 15. Okt 2012 13:07

AW: query.open in Threads
 
* aus einem Thread darf man nicht auf VCL Objekte des Hauptthreads zugreifen - ausser wenn der Zugriff mit synchronize geschützt wird
* im Thread MThread.Synchronize(doprogress) aufzurufen ist unnötig, stattdessen einfach Synchronize(doprogress) genügt
* Sind die Datenbankconnection threadsafe? Falls nein oder unbekannt, muss der Thread eine eigene Connection verwenden

Bernhard Geyer 15. Okt 2012 13:09

AW: query.open in Threads
 
Für Grid-Anzeigen empfiehlt es sich mit Limit/Top zu arbeiten um die Anzahl der Datensätze und die Dauer des Abarbeitens zu beschränken.
Dann braucht man keine Threads.

mcinternet 15. Okt 2012 13:17

AW: query.open in Threads
 
Zitat:

Zitat von Bernhard Geyer (Beitrag 1187087)
Für Grid-Anzeigen empfiehlt es sich mit Limit/Top zu arbeiten um die Anzahl der Datensätze und die Dauer des Abarbeitens zu beschränken.
Dann braucht man keine Threads.

Weil wir hier wirklich sehr Datenbanklastig arbeiten, wollen wir diese Dinge durch Threads "kapseln", da diese immer, gerade bei den langsameren Rechnern für ein Hängen der Rechner sorgen.

Gruss

Mc

mcinternet 15. Okt 2012 13:20

AW: query.open in Threads
 
Zitat:

Zitat von mjustin (Beitrag 1187086)
* aus einem Thread darf man nicht auf VCL Objekte des Hauptthreads zugreifen - ausser wenn der Zugriff mit synchronize geschützt wird

Genau dafür ist der Thread ja da - er soll an dieser Stelle das Hängen verhindern.
Zitat:

* im Thread MThread.Synchronize(doprogress) aufzurufen ist unnötig, stattdessen einfach Synchronize(doprogress) genügt
* Sind die Datenbankconnection threadsafe? Falls nein oder unbekannt, muss der Thread eine eigene Connection verwenden
Der Thread hat ja eine eigene Connection bzw. diese Form, wo der Thread gestartet wird.
Ob die Developer Expresstools und die Oracle Komponenten von Devart threadsafe sind kann ich nicht sagen. Gilt es zu checken.

Gruss
Mc

Bummi 15. Okt 2012 13:23

AW: query.open in Threads
 
So wie der Thread jetzt geschrieben ist ist er nicht nur sinnfrei sondern auch gefährlich ...

mcinternet 15. Okt 2012 13:32

AW: query.open in Threads
 
Zitat:

Zitat von Bummi (Beitrag 1187093)
So wie der Thread jetzt geschrieben ist ist er nicht nur sinnfrei sondern auch gefährlich ...

Es ist ja auch noch eine Spielwiese, wo ich die generelle Funktionalität teste.

Besserer Vorschlag?

Gruss

Mc

himitsu 15. Okt 2012 13:41

AW: query.open in Threads
 
Zitat:

Delphi-Quellcode:
procedure TMyThread.Execute;
begin
  ...
  MThread.Synchronize(doprogress);
  ...

Das mit dem MThread wurde ja schon gesagt.
Wenn man in einer Klasse ist, dann Self verwenden (oder weglassen und das Self implizit nutzen).

Denn rate mal was passiert, wenn zu z.B. 2 Threads erstellst. :zwinker:
Dann greifst du nicht mehr auf den "aktuellen" Thread zu, sondern auf den, welcher in der globalem Variable liegt.

Nja, das mit der VCL und Threads wurde auch schon genannt.
Was du machen kannst, ist das Query vom Grid (der VCL) trennen, zu aktualisieren (im thread) und es dann wieder zu verbinden (im Hautthread).

Oder die Daten im Hintergrund zu laden und dann synchronisiert in ein weiteres Dataset zu kopieren, welches am Grid hängt.



kleiner Trick, für kurze Sync-Funktionen:
Delphi-Quellcode:
procedure TMyThread.Execute;
begin
  ...
  Synchronize(procedure
    begin
      FormUsed.Update;
    end);
  ...

mjustin 15. Okt 2012 13:48

AW: query.open in Threads
 
Zitat:

Zitat von mcinternet (Beitrag 1187090)
Zitat:

Zitat von mjustin (Beitrag 1187086)
* aus einem Thread darf man nicht auf VCL Objekte des Hauptthreads zugreifen - ausser wenn der Zugriff mit synchronize geschützt wird

Genau dafür ist der Thread ja da - er soll an dieser Stelle das Hängen verhindern.

Damit meinte ich speziell diese Aufrufe von FormUsed aus Execute:

Delphi-Quellcode:
procedure TMyThread.Execute;
begin
  ...
  FormUsed.OraSessCommon.Connect;
  ...
  FormUsed.qry_searchMA.Open;
...
end;
Das ist ein Zugriff auf Objekte, die gleichzeitig auch im Hauptthread angesprochen werden.

mcinternet 15. Okt 2012 14:00

AW: query.open in Threads
 
Zitat:

Damit meinte ich speziell diese Aufrufe von FormUsed aus Execute:
Delphi-Quellcode:
procedure TMyThread.Execute;
begin
  ...
  FormUsed.OraSessCommon.Connect;
  ...
  FormUsed.qry_searchMA.Open;
...
end;
wie kann ich das an dieser Stelle korrekt abbilden?

Gruss
MC

Furtbichler 15. Okt 2012 15:39

AW: query.open in Threads
 
Zwei Möglichkeiten:
1. Du übergibst dem Thread als Parameter im Create (oder als Property) eine Oracle Connection
2. Du lässt den Thread im Execute eine eigene Connection aufbauen.

Medium 16. Okt 2012 01:28

AW: query.open in Threads
 
Ich hatte diese Konstellation bisher so noch nicht, aber ich meine folgendes Problem macht das in der gewünschten Form unmöglich:

SQL Connections sind i.A. an den Thread gebunden, in dessen Kontext man sie erstellt. Heisst: Ein TThread kann/darf eine Connection, die auf dem Formular liegt, in keiner Weise nutzen. Das beinhaltet auch, dass im Thread keine Query benutzt werden kann/darf, die als Connection die auf dem Formular benutzt. Umgekehrt das selbe: Wird die Connection im Kontext des TThreads erzeugt, darf nichts auf dem Formular etwas anfassen, was diese Connection benutzt.
Folgerung:
Connection auf Formular -> Kein Query.Open im Thread.
Connection im Thread -> Keine Benutzung der Query als Dataset für das Grid

Man könnte jetzt an eine vermittelnde Pufferstruktur denken, die du im Thread voll machst, und dann dem Hauptthread am besten per Message mitteilst, dass es vollbracht ist. Der füllt dann ein Grid damit... und schon braucht dieser vermutlich ähnlich lange, genau dies zu tun. Darüber hinaus kann das dann kein DBGrid mehr sein, bzw. ist jeglicher Bezug zur DB dahin -> Editieren wird so einfach nichts.

Alles was man versuchen würde das eigentliche Problem zu kaschieren, wird mindestens extrem wackelig. Mach es lieber richtig, und filter wie schon irgendwo vorher gesagt wirklich nur die Daten, die du gerade zur Anzeige brauchst heraus. Dauert das auch zu lange, ist dein DB Design optimierungsbedürftig. Mit einem Thread wird das aber definitiv nur eine Zugriffsfehlerhölle ohne Gleichen.

Oder um deine Frage zu beantworten:
Zitat:

wie kann ich das an dieser Stelle korrekt abbilden?
Nein ;)

Bummi 16. Okt 2012 06:15

AW: query.open in Threads
 
hatten wir das Thema nicht schon vor ein paar Tagen?
http://www.delphipraxis.net/170838-o...ry-thread.html


Alle Zeitangaben in WEZ +1. Es ist jetzt 17:10 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