[Delphi5]Query in een TThread object: Maar hoe?

Pagina: 1
Acties:

  • Aetje
  • Registratie: September 2001
  • Laatst online: 18-12-2025

Aetje

Troubleshooting met HAMERRR

Topicstarter
In een ander topic (http://gathering.tweakers.net/forum/list_messages/411754/1) vroeg ik hoe een query tijdens het processen te onderbreken is. Welnu, ik ben inmiddels aan het proberen dit op te lossen door de query in een nieuwe thread te starten. Echter, ik blijf tegen muren aan lopen. Queries kan ik niet binnen een TThread object aanmaken met owner "Self". Als ik dus het form, of de application (of nil) als owner meegeef, tijdens de create, zal de query niet stoppen en mee vernietigd worden wanneer ik de thread stop met TerminateThread.

Heeft iemand hier een tip voor? Kan ik bv een Form als container gebruiken, en die in een aparte tread starten? Per default lijkt alleen het main form een aparte tread te zijn...

Forget your fears...
...and want to know more...


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 18:56

Creepy

Tactical Espionage Splatterer

Uit de Delphi Help van TThread:
procedure Terminate;

Description

Terminate sets the threads Terminated property to True, signaling that the thread should be terminated as soon as possible. Unlike the Windows API TerminateThread, which forces the thread to terminate immediately, the Terminate method merely requests that the thread terminate. This allows the thread to perform any cleanup before it shuts down.

For Terminate to work, the thread's Execute method and any methods that Execute calls should check Terminated periodically and exit when it's True.
de .terminate van een tread sluit de thread niet af. Je moet ZELF in de execute van een thread checken of je moet stoppen, en zo ja, alles afsluiten. Dus kan je ook de tquery een .free geven.

Ook heeft de tthread een onterminate event waarin je de tquery kan free'en (voor het geval dat je je applicatie afsluit zonder een .terminate te geven aan de thread)

(hint: dit staat echt allemaal in de help file)

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Aetje
  • Registratie: September 2001
  • Laatst online: 18-12-2025

Aetje

Troubleshooting met HAMERRR

Topicstarter
Zoek eens op TerminateThread (Win32 API sectie). Die sluit een thread namelijk WEL geforceerd af. Probleem is nu alleen nog hoe ik die query IN de thread krijg. Iets als:
code:
1
2
3
4
5
6
Procedure TMyThread.Execute;
var Query1: TQuery;

begin
Query1:=TQuery.Create(Self);
...

werkt niet. TThread is namelijk geen TComponent, en dus wil die $@^$^ Query daar niet aan. Hoe los ik dat op?

Forget your fears...
...and want to know more...


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 18:56

Creepy

Tactical Espionage Splatterer

tquery.create(nil);
en dan in de onterminate event de query sluiten en free'en lijkt me..

Of een datamodule gebruiken, waarin die query staat. Zorg er dan wel voor dat je die query alleen gebruikt in de tthread.
Als je dan de thread geforceerd afsluit, blijft wel de query open staan, maar die wordt vanzelf gefreed op het moment dat de applicatie afsluit, omdat de datamodule dan ook gedestroyed wordn.

Je kan ook een afgeleide maken van de TThread (dit wordt ook sterk aangeraden btw). Dan kan je de tquery opnemen in het private gedeelte, en aanmaken in de create, en free'en in de destroy.
Doe eens file -> new -> ThreadObject.. noem em TQueryThread o.i.d. en er is meteen een afgeleide gemaakt. Zelf ff de create en destroy aanmaken (overriden en inherited gebruiken!) en klaar.

edit:

Zoiets dus. Ik weet alleen niet wat er gebeurd als de query.close wordt aangeroepen als ie nog bezig is met het uitvoeren van de query zelf (m.b.v. .open of .execsql)
[code]type
TQueryThread = class(TThread)
private
{ Private declarations }
Query: TQuery;
protected
procedure Execute; override;
public
constructor create;
destructor destroy; override;
end;

implementation

procedure TQueryThread.Execute;
begin
{ Place thread code here }
//
end;

constructor TQueryThread.create;
begin
Query:=TQuery.create(nil);
end;

destructor TQueryThread.destroy;
begin
//Terminate;
Query.Close;
Query.Free;
inherited;
end;[/code]

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Aetje
  • Registratie: September 2001
  • Laatst online: 18-12-2025

Aetje

Troubleshooting met HAMERRR

Topicstarter
Ik denk dat je mn probleem niet geheel begrijpt. De query duurt soms lang om uit te voeren. Dus dan blijft de thread op de Query1.Open "hangen". Op dat moment wil ik de query kunnen annuleren. Daar die ^&*%* query objecten geen CancelSQL of zoiets kennen, ben ik genoodzaakt het component in zn geheel te vernietigen.

Mijn huidige poging behelst inderdaad het creeren van de query in de thread (niet onder private maar onder public) en lijkt te werken. Overigens weet ik niet of bij het veranderen van de TThread.Terminate de uitvoering van de thread (die dan op de query te stampen staat) tot staan brengt. Ik denk nl van niet!
Dus rest me nog 1 ding, tenzij ik wat vergeten ben: De Win32 API procedure TerminateThread. Een soort CTRL-ALT-DEL voor threads. Maar deze laat geen user-gegenereerde code tot uitvoering komen. Een FREE op de query is dus niet mogelijk dan (en het geheugen ervan gaat waarschijnlijk verloren, tenzij er een manier is om dat geheugengedeelte na terminatie van de query BUITEN de (al getermineerde) tread te freeen.

Forget your fears...
...and want to know more...


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 18:56

Creepy

Tactical Espionage Splatterer

Dat kan toch als je de query buiten de Thread creeert? (dus m.b.v. een datamodule?)

Je kan dan echter alleen de applicatie pas afsluiten als de query klaar is met uitvoeren, omdat eerder de datamodule niet wordt gesloten.

Hmm.. echt netjes is het natuurlijk niet.. kan je niet het 1 en ander optimizen aan de query of db ontwerp?

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Aetje
  • Registratie: September 2001
  • Laatst online: 18-12-2025

Aetje

Troubleshooting met HAMERRR

Topicstarter
|:( maar het hele punt is om de query te kunnen onderbreken, zodat je geen uren zit te wachten opdat een query klaar is met uitvoeren. Bovendien wil ik die query een lagere CPU-prioriteit geven. Als ik de query op de datamodule zet ligt het main programma alsnog stil tijdens het uitvoeren van die query.

Forget your fears...
...and want to know more...


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 18:56

Creepy

Tactical Espionage Splatterer

Hmm? Vanuit de thread de datamodule aanspreken natuurlijk.. dan "hangt" alleen de thread.. en niet je form toch? (lijkt me tenminste.. nooit getest).

De query echt onderbreken zal niet gaan denk ik (tenminste niet met de standaard bde componenten)

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 18:56

Creepy

Tactical Espionage Splatterer

Single DB Query Thread v.2.0.0 FW 802 Kb 03.07.01
By Les Howell. Component that allows Threaded Queries to be performed without freezing you application. It supports standard Data Aware (TQuery, TStored Proc) and Interbase (TIBDataSet and TIBSQL). Interbase is for Delphi 5 only. The components include a Cancel Button which can be attached to your application or free form on center of screen. Events to handle data set requests, thread done and cancel.

Fully functional
Source: On purchase/registration
Exe-demo included
Source Price: $7
Download: D3 D4 D5
Is dat niks? :)

http://www.torry.net/db/other/db_other/singlethread.zip
Link is dus zonder source..

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Aetje
  • Registratie: September 2001
  • Laatst online: 18-12-2025

Aetje

Troubleshooting met HAMERRR

Topicstarter
Da's wat ik wil!!!

Alleen, nu de source nog, want ik gebruik TtaQuery en niet TQuery, moet dat dus aanpassen... Najaa, vanavond eens even GRONDIG zoeken...

Forget your fears...
...and want to know more...


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 18:56

Creepy

Tactical Espionage Splatterer

ach.. $7 voor de source.. wat is nou 7 dollar? :)

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Aetje
  • Registratie: September 2001
  • Laatst online: 18-12-2025

Aetje

Troubleshooting met HAMERRR

Topicstarter
LOL Source zat er bij. Shareware, maar included... En die package (die freeware is) is enigzins buggy. Naja, met de source erbij compileer ik mn eigen component wel. :) Bedankt voor deze tip!!!

Forget your fears...
...and want to know more...


  • Aetje
  • Registratie: September 2001
  • Laatst online: 18-12-2025

Aetje

Troubleshooting met HAMERRR

Topicstarter
Ja, ik weet, oud topic.

Het component dat hier gepost was is buggy... Wil nie op het form in Delphi 5.

Maar voor degenen die met het zelfde probleem zitten of gewoon random geinteresseerd zijn, paste het volgende ns in een PAS file. Zelf gebouwd, en stabiel:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
unit QThread;

// Author: F. Koning

// Function: Run a standard SQL query in a seperate thread, and allow for
// immediate cancellation of the query.

// Start date: 25-3-'02
// Last modified date: 27-3-'02
// Last modification: Database requirement added. Code cleanup.

// Notes: This unit applies a few tricks in order to make it work. When the
// thread is cancelled with TerminateThread, the application calling the
// query could no longer free the query. (It seemed to remain in a semi-busy
// state). Feeding it a bogus SQL statement, running it anew, capturing the
// error it creates, thus resetting the query, works.
// The query runs by default in tpLowest, just above the idle processes. This
// allows for the system to remain responsive while the query runs in the
// background.
// Call CreateQueryThread to create the thread and recieve a pointer to the
// Thread Object. Call CancelThread to end the thread. You can run multiple
// threads next to eachother... However, keep in mind that multiple threads
// eat resources. Apart from that, the thread does NOT have it's own query
// component. It needs to be fed one.
// Also, a Database and Session object are mandatory. (Session to be able to
// work with multiple threads, Database to ensure the thing is connected).

// The Datasource supplied to the QueryThread will automatically be connected
// to the dataset once it is ready (or not at all, if the thread is cancelled).

// The QThreadComp component is but a wrapper for easy use. It'll check the
// input as well. Use the type if you want to, at your own risk... :)

// Known problems: Killing several queries tends to run the database out of
// resources while debugging. Unknown if the problem exists at runtime.


interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Grids, DBGrids, DB, DBTables;

type
  TQueryThread = class(TThread)
  private
    FSession: TSession;
    FQuery: TQuery;
    FDatasource: TDatasource;
    FQueryException: Exception;
    FResetThread: Boolean;
    procedure ConnectDataSource;
    procedure ShowQryError;
    procedure ResetThread;
  protected
    procedure Execute; override;
  public
    constructor Create(Session: TSession; Query: TQuery; DataSource: TDataSource; vPriority: tThreadPriority); virtual;
    procedure CancelThread;
  published
  end;

type TQThreadComp = Class (TComponent)
  private
    FQuery:TQuery;
    FSession:TSession;
    FDataSource:TDataSource;
    FDataBase:TDataBase;
    FQueryThread:TQueryThread;
    FPriority: TThreadPriority;
  protected
  public
    Constructor Create(AOwner:TComponent); Override;
    procedure CancelThread;
    procedure Run;
  published
    property Database:TDataBase read FDataBase write FDataBase;
    property Session: TSession read FSession write FSession;
    property Query: TQuery read FQuery write FQuery;
    property Datasource: TDataSource read FDatasource write FDatasource;
    property Priority: TThreadPriority read FPriority write FPriority default tpLowest;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Threaded Objects',[TQThreadComp]);
end;

constructor TQueryThread.Create(Session: TSession; Query: TQuery; Datasource: TDataSource; vPriority: TThreadPriority);
begin
  inherited Create(True);    // Create thread in a suspendend state: To apply settings
  FSession := Session;   // connect all private fields
  FQuery := Query;
  FDataSource := Datasource;
  FreeOnTerminate := true; // Free it when done
  Priority:=vPriority; // Set priority. Usually below normal (tpLowest).
  Resume; // Thread must be resumed to start
  Sleep(10);
end;

procedure TQueryThread.ResetThread;
var TempStrings: TStringList;
begin
  FQuery.Close;
  TempStrings := TStringList.Create; {Save the current Query}
  try
    TempStrings.Assign(FQuery.SQL);
    try
    FQuery.SQL.Clear;
    FQuery.Open; // That won't work. So it'll stop immediately with a EDataBaseError.
    except
    else ; // Nothing here, the error in the (empty) query is expected.
    end;
  finally
    FQuery.Close; // Should be no need to.
    FQuery.SQL.Assign(TempStrings);
    TempStrings.free;
  end
end;

procedure TQueryThread.Execute;
begin
  if FResetThread then Synchronize(ResetThread) // This will reset the query. Do not remove,
  else                          // application will hang if an unreset query
    try                          // exists on program termination.
    FQuery.Open;
    Synchronize(ConnectDataSource); // Must be done in the main VCL thread to avoid access violations.
    except // Capture exception, if one occurs, and handle it in the context of
         // the main thread (Synchonize used for this purpose).
    FQueryException := ExceptObject as Exception;
    Synchronize(ShowQryError);
    end;
end;

procedure TQueryThread.ConnectDataSource;
begin
  FDataSource.DataSet := FQuery;  // Connect the DataSource to the TQuery
end;

procedure TQueryThread.ShowQryError;
begin
  Application.ShowException(FQueryException); // Handle the exception
end;

procedure TQueryThread.CancelThread;
var iThreadState:Cardinal;
begin
  if GetExitCodeThread(Handle,iThreadState) then
    if (iThreadState = STILL_ACTIVE) then // check if it's still running
    begin
    Suspend; // Stop at once.
    TerminateThread(Handle,0); // Boom! Not very gentle, but I think this is the only way to interrupt a running query.
    FResetThread := True;
    Execute; // Make sure it resets. Executing while FResetThread is true does this.
    end;
end;

{ TQThreadComp }

procedure TQThreadComp.CancelThread;
begin
  FQueryThread.CancelThread;
end;

constructor TQThreadComp.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  Priority := tpLowest; // Default.
end;

procedure TQThreadComp.Run;
begin
// Check some input before running.
  if assigned(FDataSource) and assigned(FSession) and assigned(FQuery) and assigned(FDatabase) then
    begin
    // Must be unbound, to prevent conflicts.
    FDataSource.DataSet := nil;
    FSession.Close;
    FQuery.Close;
    FDatabase.Close;
    // Check if all components are properly connected
    If (FSession.SessionName = '') then FSession.AutoSessionName := true;
    FQuery.SessionName := FSession.SessionName;
    FDatabase.SessionName := FSession.SessionName;
    FQuery.DatabaseName := FDatabase.DatabaseName;
    // Make sure the database connection is there. Do NOT remove this. If a
    // query attempts to generate a login screen from within the query thread,
    // it will fail!
    FDatabase.Connected := true;
    FSession.Active := true;
    // All ok? Then run...
    FQueryThread:=TQueryThread.Create(FSession,FQuery,FDataSource,FPriority); // And run.
    end
  else
    MessageDLG('TQThreadComp: Must supply Database, Session, Query and Datasource. Query cancelling.',mtError,[mbOK],0);
end;

end.

[edit]
Voor alle duidelijkheid: Dit is een component, geen programma.

Forget your fears...
...and want to know more...


  • Delphi32
  • Registratie: Juli 2001
  • Laatst online: 01:40

Delphi32

Heading for the gates of Eden

Ok gelezen, ziet er goed uit. Mag ik wat vragen/opmerkingen stellen bij je code?
code:
1
2
3
4
5
6
7
8
constructor TQueryThread.Create(
  Session: TSession; 
  Query: TQuery; Datasource: 
  TDataSource; vPriority: TThreadPriority);
begin
...
  Sleep(10);
end;

Vanwaar de Sleep(10)? Wat heeft het voor zin om 10 msecs te wachten?

En dan deze:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
procedure TQueryThread.ResetThread;
var TempStrings: TStringList;
begin
  FQuery.Close;
  TempStrings := TStringList.Create; {Save the current Query}
  try
    TempStrings.Assign(FQuery.SQL);
    try
    ...
    end;
  finally
    FQuery.Close; // Should be no need to.
    FQuery.SQL.Assign(TempStrings);
    TempStrings.free;
  end
end;

Wat als FQuery.Close crasht? Wat als FQuery.SQL.Assign crasht? In beide gevallen wordt tempStrings niet gefreed.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure TQThreadComp.Run;
begin
// Check some input before running.
  if (
    assigned(FDataSource) 
    and assigned(FSession) 
    and assigned(FQuery) 
    and assigned(FDatabase)) then
    begin
    ...
    end
  else
    MessageDLG('TQThreadComp: Must supply Database, Session,
Query and Datasource. Query cancelling.',mtError,[mbOK],0);
end;

Probeer dit eens op te lossen met een Assert. Je toont nu een Messagebox die je in principe nooit aan je users wil laten zien.
Dus bv:
code:
1
2
3
4
5
6
7
8
9
10
11
12
procedure TQThreadComp.Run;
begin
// Check some input before running.
  Assert(
    Assigned(FDataSource) 
    and Assigned(FSession) 
    and Assigned(FQuery) 
    and Assigned(FDatabase),
    'TQThreadComp: Must supply DataSource, Session, 
    Query and Database. Operation cancelled'
  );  
  //ga hier gewoon verder.

Ook aardig is om deze zaken, die dus blijkbaar nodig zijn voordat je de query kunt runnen en die al meegegeven worden in de Create, reeds in de Create op Assigned te testen (als dat in dit geval kan natuurlijk).

Begrijp me goed, ik vind dat je hier een leuk stukje code hebt geplaatst en ik ga het dan ook zeker gebruiken. Mijn opmerkingen zijn er alleen op gericht om mijn ervaringen op het gebied van stabiliteit en eenvoud met je te delen :*

edit:
Layout een beetje verbeterd >:)

Verwijderd

Delphi32: Vanwaar de Sleep(10)?
Mbv Sleep(0) wordt geswitched naar een andere thread. De 10 is denk ik voor de zekerheid :)

Zover ik kan bekijken is de truc in ResetThread nodig om de protected method CloseCursor van de query aan te roepen; dat kan echter ook als volgt:
code:
1
2
3
4
5
6
7
type
  THackQuery = class(TQuery);

procedure TQueryThread.ResetThread;
begin
  THackQuery(FQuery).CloseCursor;
end;

Verder zou ik niet 1 component, 1 thread maken; maar 1 component, meerdere threads.

En TerminateThread(Handle,0); is wel zeer smerig :)
Pagina: 1