What is the best way to write a Delphi DUnit test for a TThread descendant when FreeOnTerminate = True? The TThread descendant returns a reference which I need to test for, but I can't figure out how to wait for the thread to finish in the test...
unit uThreadTests;
interface
uses
Classes, TestFramework;
type
TMyThread = class(TThread)
strict private
FId: Integer;
protected
procedure Execute; override;
public
constructor Create(AId: Integer);
property Id: Integer read FId;
end;
TestTMyThread = class(TTestCase)
strict private
FMyId: Integer;
procedure OnThreadTerminate(Sender: TObject);
protected
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestMyThread;
end;
implementation
{ TMyThread }
constructor TMyThread.Create(AId: Integer);
begin
FreeOnTerminate := True;
FId := AId;
inherited Create(False);
end;
procedure TMyThread.Execute;
begin
inherited;
FId := FId + 1;
end;
{ TestTMyThread }
procedure TestTMyThread.TestMyThread;
//var
// LThread: TMyThread;
begin
// LThread := TMyThread.Create(1);
// LThread.OnTerminate := OnThreadTerminate;
// LThread.WaitFor;
// CheckEquals(2, FMyId);
// LThread.Free;
///// The above commented out code is only useful of FreeOnTerminate = False;
with TMyThread.Create(1) do
begin
OnTerminate := OnThreadTerminate;
WaitFor; /// Not sure how else to wait for the thread to finish?
end;
CheckEquals(2, FMyId);
end;
procedure TestTMyThread.OnThreadTerminate(Sender: TObject);
begin
FMyId := (Sender as TMyThread).Id;
end; /// When FreeOnTerminate = True - THIS LINE CAUSES ERROR: Thread Error the handle is invalid
procedure TestTMyThread.SetUp;
begin
inherited;
end;
procedure TestTMyThread.TearDown;
begin
inherited;
end;
initialization
RegisterTests([TestTMyThread.Suite]);
end.
Any ideas would be welcomed.
Delphi 2010.
Subclass the thread to make it more testable. TThread and TObject provide enough hooks that you can add sensing variables to observe that it reaches certain points with the states you want it to have.
I see three aspects to this particular class that you might wish to test:
It computes a value for its Id property based on the value sent to the constructor.
It computes the new Id property in the new thread, not the thread that calls the constructor.
It frees itself when it's finished.
All those things are testable from a subclass, but hard to test otherwise without making changes to the thread's interface. (All the other answers so far require changing the thread's interface, such as by adding more constructor arguments or by changing the way it starts itself. That can make the thread harder, or at least more cumbersome, to use in the real program.)
type
PTestData = ^TTestData;
TTestData = record
Event: TEvent;
OriginalId: Integer;
FinalId: Integer;
end;
TTestableMyThread = class(TMyThread)
private
FData: PTestData;
public
constructor Create(AId: Integer; AData: PTestData);
destructor Destroy; override;
procedure AfterConstruction; override;
end;
constructor TTestableMyThread.Create(AId: Integer; const AData: PTestData);
begin
inherited Create(AId);
FData := AData;
end;
destructor TestableMyThread.Destroy;
begin
inherited;
FData.FinalId := Id;
// Tell the test that the thread has been freed
FData.Event.SetEvent;
end;
procedure TTestableMyThread.AfterConstruction;
begin
FData.OriginalId := Id;
inherited; // Call this last because this is where the thread starts running
end;
Using that subclass, it's possible to write a test that checks the three qualities identified earlier:
procedure TestTMyThread.TestMyThread;
var
Data: TTestData;
WaitResult: TWaitResult;
begin
Data.OriginalId := -1;
Data.FinalId := -1;
Data.Event := TSimpleEvent.Create;
try
TTestableMyThread.Create(1, #Data);
// We don't free the thread, and the event is only set in the destructor,
// so if the event is signaled, it means the thread freed itself: That
// aspect of the test implicitly passes. We don't want to wait forever,
// though, so we fail the test if we have to wait too long. Either the
// Execute method is taking too long to do its computations, or the thread
// isn't freeing itself.
// Adjust the timeout based on expected performance of Execute.
WaitResult := Data.Event.WaitFor(5000);
case WaitResult of
wrSignaled: ; // This is the expected result
wrTimeOut: Fail('Timed out waiting for thread');
wrAbandoned: Fail('Event was abandoned');
wrError: RaiseLastOSError(Data.Event.LastError);
else Fail('Unanticipated error waiting for thread');
end;
CheckNotEquals(2, Data.OriginalId,
'Didn''t wait till Execute to calculate Id');
CheckEquals(2, Data.FinalId,
'Calculated wrong Id value');
finally
Data.Event.Free;
end;
end;
Because you made the thread free itself upon termination then you have asked it to destroy all traces of itself as soon as it is done. Since you cannot exert influence on when it finishes, it is wrong to refer to anything inside the thread after you start it.
The solutions proposed by other, namely asking the thread to signal you when it terminates, are good. I personally would probably elect to do it that way. If you use an event as a signal then you can wait on that event.
However, there is another way to do it.
Create the thread suspended.
Duplicate the thread handle.
Start the thread.
Wait on the duplicated handle.
Because you own the duplicated handle, rather than the thread, you are safe to wait on it. It seems a little more complicated, but I suppose it avoids creating an extra synchronization object where one is not needed. Note that I'm not advocating this approach over the approach of using an event to signal completion.
Anyway, here's a simple demonstration of the idea.
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, Classes;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
public
destructor Destroy; override;
end;
destructor TMyThread.Destroy;
begin
Writeln('I''m dead!');
inherited;
end;
procedure TMyThread.Execute;
begin
end;
var
DuplicatedHandle: THandle;
begin
with TMyThread.Create(True) do // must create suspended
begin
FreeOnTerminate := True;
Win32Check(DuplicateHandle(
GetCurrentProcess,
Handle,
GetCurrentProcess,
#DuplicatedHandle,
0,
False,
DUPLICATE_SAME_ACCESS
));
Start;
end;
Sleep(500);
Writeln('I''m waiting');
if WaitForSingleObject(DuplicatedHandle, INFINITE)=WAIT_OBJECT_0 then
Writeln('Wait succeeded');
CloseHandle(DuplicatedHandle);
Readln;
end.
Create the thread in a suspended state, then set the OnTerminate and finally Resume the thread.
In your test class, define a private boolean field FThreadDone which is initialized with false and set to true by the OnTerminate Eventhandler.
Also, your constructor logic is a bit dirty, as you should not initialize field prior to calling the inherited constructor.
So:
constructor TMyThread.Create(AId: Integer);
begin
inherited Create(true);
FreeOnTerminate := True;
FId := AId;
end;
...
procedure TestTMyThread.TestMyThread;
begin
FThreadDone := False;
with TMyThread.Create(1) do begin // Note: Thread is suspended...
OnTerminate := OnThreadTerminate;
// Resume; // ... and finally started here!
Start;
end;
While not FThreadDone do Application.ProcessMessages;
CheckEquals(2, FMyId);
end;
procedure TestTMyThread.OnThreadTerminate(Sender: TObject);
begin
FMyId := (Sender as TMyThread).Id;
FThreadDone := True;
end;
This should do the job.
EDIT: Corrected stupid corrections, tested, works.
Here is an example using an anonymous thread.
An event (TSimpleEvent) is created
An anonymous thread executes the test thread and
Waits for the event, which signals in the OnTerminate handler of the test thread
The anonymous thread is on hold until executed with a WaitFor
The result was picked up by the OnTerminate handler
The important thing here is that the event is waited for in a thread. No dead-lock situation.
Uses
SyncObjs;
type
TMyThread = class(TThread)
private
FId : Integer;
protected
procedure Execute; override;
public
constructor Create( anInt : Integer);
property Id : Integer read FId;
end;
TestTMyThread = class
strict private
FMyId: Integer;
FMyEvent : TSimpleEvent;
procedure OnThreadTerminate(Sender: TObject);
protected
public
procedure TestMyThread;
end;
{ TMyThread }
constructor TMyThread.Create(anInt : Integer);
begin
inherited Create(True);
FreeOnTerminate := True;
FId := anInt;
end;
procedure TMyThread.Execute;
begin
Inc(FId);
end;
procedure TestTMyThread.TestMyThread;
var
AnonThread : TThread;
begin
FMyEvent := TSimpleEvent.Create(nil,true,false,'');
try
AnonThread :=
TThread.CreateAnonymousThread(
procedure
begin
With TMyThread.Create(1) do
begin
OnTerminate := Self.OnThreadTerminate;
Start;
end;
FMyEvent.WaitFor; // Wait until TMyThread is ready
end
);
AnonThread.FreeOnTerminate := False;
AnonThread.Start;
AnonThread.WaitFor; // Wait here until test is ready
AnonThread.Free;
Assert(FMyId = 2); // Check result
finally
FMyEvent.Free;
end;
end;
procedure TestTMyThread.OnThreadTerminate(Sender: TObject);
begin
FMyId := (Sender as TMyThread).Id;
FMyEvent.SetEvent; // Signal TMyThread ready
end;
Update, since Delphi-2010 does not have an anonymous thread class, here is an alternative which you can implement:
Type
TMyAnonymousThread = class(TThread)
private
FProc : TProc;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended,SelfFree: Boolean; const aProc: TProc);
end;
constructor TMyAnonymousThread.Create(CreateSuspended,SelfFree: Boolean;
const aProc: TProc);
begin
Inherited Create(CreateSuspended);
FreeOnTerminate := SelfFree;
FProc := aProc;
end;
procedure TMyAnonymousThread.Execute;
begin
FProc();
end;
Related
I have several threads, each with its own TEvent:
TWorkerThread = class(TThread)
private
FSignal: TEvent;
public
constructor Create;
destructor Destroy; override;
procedure Execute; override;
end;
procedure TWorkerThread.Execute;
var ID: integer;
begin
while not Terminated do begin
Tmonitor.Enter(MainQueue);
try
if (MainQueue.Count > 0) then ID := MainQueue.dequeue
else ID := 0;
finally
Tmonitor.exit(MainQueue);
end;
if (ID <> 0) then ProcessID(ID)
else fSignal.WaitFor(INFINITE);
end;
end;
Now, in the main thread I would like to retrieve the list of threads that are waiting for the signal, so those who are doing fSignal.WaitFor(INFINITE);
Actually I do like this in the code:
procedure AddIDToTheQueue(const id: integer);
begin
Tmonitor.Enter(MainQueue);
try
MainQueue.Enqueue(id);
//signal all background thread !!! this a don't like i prefer to signal only one thread who are waiting instead of signaling all the threads !!!
for I := Low(workerThreads) to High(workerThreads) do
workerThreads[i].Signal.SetEvent;
finally
Tmonitor.Exit(MainQueue);
end;
end;
but it's not efficient as I signal all threads instead of signalling only the first one that is waiting for the signal. How can I do this?
To directly answer your question, you can always check if an event is signalled using :
if FSignal.WaitFor(0) = wrSignaled then begin
// event is signaled
end;
If you need to handle all cases, of course :
case FSignal.WaitFor(0) of
wrSignaled:;
wrTimeout:;
wrAbandoned:;
wrError:;
wrIOCompletion:;
end;
However - this is an ideal situation to leverage the built-in threadpool by simply using tasks.
For example, all of your code can be eliminated and reduced to this :
procedure AddIDToTheQueue(id: integer);
var
LTask : ITask;
begin
LTask := TTask.Create(procedure begin ProcessID(id); end);
LTask.Start;
end;
There is no sense in passing an integer as const. Whether you pass the value or a pointer, it's the same size. If you're not modifying it, it makes no difference.
I would not use events in this situation.
I would use yield.
Then Execute becomes
procedure TWorkerThread.Execute;
var
ID : integer;
begin
while not Terminated do begin
Tmonitor.Enter(MainQueue);
try
if (MainQueue.Count > 0) then ID := MainQueue.dequeue
else ID := 0;
finally
Tmonitor.exit(MainQueue);
end;
if (ID <> 0) then ProcessID(ID)
else Yield;
end;
end;
and AddIDToTheQueue becomes
procedure AddIDToTheQueue(const id: integer);
begin
Tmonitor.Enter(MainQueue);
try
MainQueue.Enqueue(id);
finally
Tmonitor.Exit(MainQueue);
end;
end;
You are not attempting to tell your threads that a new ID is available. You are letting the first available thread handle it automatically.
Calling TEvent.WaitFor(0) it's a bit slower... You cand make a new beautiful Event that uses Critical Section to fast check for singaled state. In this example, I also added a value to the event which can help you send a command with that signal too.
TSafeCardinalEvent = class
private
CardinalValue: Cardinal;
CS: TRTLCriticalSection;
hEvent: THandle;
function GetEventValue: Cardinal;
procedure SetEventValue(AValue: Cardinal);
public
constructor Create; virtual;
destructor Destroy; override;
function Signaled: Boolean; overload; {$IFDEF RELEASE} inline; {$ENDIF}
function Signaled(out Code: Cardinal): Boolean; overload;
property Value: Cardinal read GetEventValue write SetEventValue;
property EventHandle: THandle read hEvent;
end;
constructor TSafeCardinalEvent.Create;
begin
inherited Create;
CardinalValue:= 0;
InitializeCriticalSection(CS);
hEvent:= CreateEvent(nil, True, False, nil);
end;
destructor TSafeCardinalEvent.Destroy;
begin
CloseHandle(hEvent);
DeleteCriticalSection(CS);
inherited Destroy;
end;
function TSafeCardinalEvent.Signaled: Boolean;
begin
EnterCriticalSection(CS);
Result:= CardinalValue > 0;
LeaveCriticalSection(CS);
end;
function TSafeCardinalEvent.Signaled(out Code: Cardinal): Boolean;
begin
EnterCriticalSection(CS);
Code:= CardinalValue;
Result:= CardinalValue > 0;
LeaveCriticalSection(CS);
end;
function TSafeCardinalEvent.GetEventValue: Cardinal;
begin
EnterCriticalSection(CS);
Result:= CardinalValue;
LeaveCriticalSection(CS);
end;
procedure TSafeCardinalEvent.SetEventValue(AValue: Cardinal);
begin
EnterCriticalSection(CS);
CardinalValue:= AValue;
if CardinalValue = 0
then ResetEvent(hEvent)
else SetEvent(hEvent);
LeaveCriticalSection(CS);
end;
I have a form with a TMemo that I want to show what is going on in several services started by the application.
What I have running:
idHTTPServer running with idContext responding to requests
a Thread downloading updates from Dropbox
idUDPServer responding to UDP requests
another thread taking care of some database stuff.
the main application thread also needed to add log
Basically, I need to know how to create a standard, unified, thread safe way to channel the log messages to my TMemo and keep the user updated of what is going on.
Since you are already using Indy anyway, you can use Indy's TIdSync (synchronous) or TIdNotify (asynchronous) class to access the TMemo safely. For simple logging purposes, I would use TIdNotify, eg:
type
TLog = class(TIdNotify)
protected
FMsg: string;
procedure DoNotify; override;
public
class procedure LogMsg(const AMsg; string);
end;
procedure TLog.DoNotify;
begin
Form1.Memo1.Lines.Add(FMsg);
end;
class procedure TLog.LogMsg(const AMsg: string);
begin
with TLog.Create do
try
FMsg := AMsg;
Notify;
except
Free;
raise;
end;
end;
Then you can directly call it in any thread like this:
TLog.LogMsg('some text message here');
UPDATE: in Delphi 2009 and later, you can use anonymous procedures with the static versions of TThread.Synchronize() and TThread.Queue(), thus making Indy's TIdSync and TIdNotify classes obsolete, eg:
type
TLog = class
public
class procedure LogMsg(const AMsg; string);
end;
class procedure TLog.LogMsg(const AMsg: string);
begin
TThread.Queue(nil,
procedure
begin
Form1.Memo1.Lines.Add(AMsg);
end
);
end;
Basically, you can build a thread that receive all the message (here, it is a function AddEvent). Messages are queued (and timestamped) and written down to the memo when possible (if you're under heavy load...).
Don't forget to clean the memo if it exceeds a number of line, add exception handling etc...
I use something like this :
TThreadedMsgEvent = class( TThread )
private
FLock : TCriticalSection;
FStr : TQueue<String>;
FMemo : TMemo;
function GetEvent : String;
protected
procedure Execute; override;
public
procedure AddEvent( aMsg : String );
constructor Create( AMemo: TMemo );
destructor Destroy; override;
end;
implementation
{ TThreadedMsgEvent }
procedure TThreadedMsgEvent.AddEvent(aMsg: String);
begin
FLock.Acquire;
FStr.Enqueue( FormatDateTime('DD/MM/YY HH:NN:SS.ZZZ',Now)+ ' : '+ aMsg );
FLock.Release;
end;
constructor TThreadedMsgEvent.Create(aMemo: TMemo);
begin
inherited Create(True);
FreeOnTerminate := False;
FOnMessage := ACallBack;
FStr := TQueue<String>.Create();
FLock := TCriticalSection.Create;
FMemo := aMemo;
Resume;
end;
destructor TThreadedMsgEvent.Destroy; override;
begin
FreeAndNil( FStr );
FreeAndNil( FLock );
end;
procedure TThreadedMsgEvent.Execute;
begin
while not Terminated do
begin
try
if (FStr.Count > 0) then
begin
if Assigned( aMemo ) then
begin
TThread.synchronize( procedure
begin
FMemo.Lines.Add( GetEvent );
end; );
end;
end;
except
end;
TThread.Sleep(1);
end;
end;
function TThreadedMsgEvent.GetEvent: String;
begin
FLock.Acquire;
result := FStr.Dequeue;
FLock.Release;
end;
You can also notify this thread with Windows Messages. It might be easier as you won't need any reference to this thread in your classes.
I have the following service written with Delphi 2007 which I find is not releasing the thread Handle. The functions CurrentMemoryUsage and GetOpenHandles are functions to return memory used and the number of handle used by the application. The Timer fires each second, creating a thread which is immediately destroyed. And I can see in my log the Number of Open Handles increments by one each time. This is a very basic threading question.
TMyThread = class(TThread)
private
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
end;
procedure TMyService.MyTimerTimer(Sender: TObject);
var
MyThread : TMyThread;
begin
MyThread := TMyThread.Create(True);
MyThread.OnTerminate := ThreadTerminated;
MyThread.FreeOnTerminate := True;
MyThread.Resume;
end;
procedure TMyThread.Execute;
begin
FreeOnTerminate := True;
end;
destructor TMyThread.Destroy;
begin
appendtolog((FormatFloat('Memory used: ,.# K', CurrentMemoryUsage / 1024))+',Number of Handles:'+inttostr(GetOpenHandles)) ;
end;
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
end;
procedure TMyService.ThreadTerminated(Sender: TObject);
begin
appendtolog('thread terminiated');
end;
You have forgotten to call the inherited Destroy. That is what frees the system resources associated with the thread.
destructor TMyThread.Destroy;
begin
appendtolog((FormatFloat('Memory used: ,.# K',
CurrentMemoryUsage / 1024))+',Number of Handles:'+inttostr(GetOpenHandles));
inherited;
end;
If I create a (suspended) thread from the main thread as such:
with TMyThread.Create(True) do
begin
OnTerminate := ThreadTerminated;
FreeOnTerminate := False;
Start;
end;
How do I go about freeing that instance once it's completed? (ie the Execute procedure has finished executing - assume I've captured exceptions).
This Proper way of destroying a tthread object link shows a way (via the PostMessage procedure) which works fine and makes sense. However, what if I create the thread and I don't have a handle to a form or something where I can invoke the PostMessage procedure. eg I create the thread within a class descended directly from TObject?
TMyClass = class
public
procedure DoSomething;
end;
TMyClass.DoSomething;
begin
with TMyThread.Create(True) do
begin
OnTerminate := ThreadTerminated;
FreeOnTerminate := False;
Start;
end;
end;
So, I guess, how do I free a thread without access to a form handle?
Thanks
Obviously, somewhere there has to be a reference to the instantiated thread. But I can relate to your wish: you want a always-done-never-care solution.
I suggest you manage the thread's existence by a separate ThreadController class:
unit Unit2;
interface
uses
Classes, SysUtils, Forms, Windows, Messages;
type
TMyThreadProgressEvent = procedure(Value: Integer;
Proceed: Boolean) of object;
procedure RunMyThread(StartValue: Integer; OnProgress: TMyThreadProgressEvent);
implementation
type
TMyThread = class(TThread)
private
FException: Exception;
FOnProgress: TMyThreadProgressEvent;
FProceed: Boolean;
FValue: Integer;
procedure DoProgress;
procedure HandleException;
procedure ShowException;
protected
procedure Execute; override;
end;
TMyThreadController = class(TObject)
private
FThreads: TList;
procedure StartThread(StartValue: Integer;
OnProgress: TMyThreadProgressEvent);
procedure ThreadTerminate(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
end;
var
FMyThreadController: TMyThreadController;
function MyThreadController: TMyThreadController;
begin
if not Assigned(FMyThreadController) then
FMyThreadController := TMyThreadController.Create;
Result := FMyThreadController
end;
procedure RunMyThread(StartValue: Integer; OnProgress: TMyThreadProgressEvent);
begin
MyThreadController.StartThread(StartValue, OnProgress);
end;
{ TMyThreadController }
constructor TMyThreadController.Create;
begin
inherited;
FThreads := TList.Create;
end;
destructor TMyThreadController.Destroy;
var
Thread: TThread;
begin
while FThreads.Count > 0 do
begin
Thread := FThreads[0]; //Save reference because Terminate indirectly
//extracts the list entry in OnTerminate!
Thread.Terminate; //Indirectly decreases FThreads.Count
Thread.Free;
end;
FThreads.Free;
inherited Destroy;
end;
procedure TMyThreadController.StartThread(StartValue: Integer;
OnProgress: TMyThreadProgressEvent);
var
Thread: TMyThread;
begin
Thread := TMyThread.Create(True);
FThreads.Add(Thread); //Add to list before a call to Resume because once
//resumed, the thread might be gone already!
Thread.FValue := StartValue;
Thread.FOnProgress := OnProgress;
Thread.OnTerminate := ThreadTerminate;
Thread.Resume;
end;
procedure TMyThreadController.ThreadTerminate(Sender: TObject);
begin
FThreads.Extract(Sender);
end;
{ TMyThread }
procedure TMyThread.DoProgress;
begin
if (not Application.Terminated) and Assigned(FOnProgress) then
FOnProgress(FValue, FProceed);
end;
procedure TMyThread.Execute;
begin
try
FProceed := True;
while (not Terminated) and (not Application.Terminated) and FProceed and
(FValue < 20) do
begin
Synchronize(DoProgress);
if not FProceed then
Break;
Inc(FValue);
Sleep(2000);
end;
//In case of normal execution ending, the thread may free itself. Otherwise,
//the thread controller object frees the thread.
if not Terminated then
FreeOnTerminate := True;
except
HandleException;
end;
end;
procedure TMyThread.HandleException;
begin
FException := Exception(ExceptObject);
try
if not (FException is EAbort) then
Synchronize(ShowException);
finally
FException := nil;
end;
end;
procedure TMyThread.ShowException;
begin
if GetCapture <> 0 then
SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
if (FException is Exception) and (not Application.Terminated) then
Application.ShowException(FException)
else
SysUtils.ShowException(FException, nil);
end;
initialization
finalization
FreeAndNil(FMyThreadController);
end.
To run this sample thread which counts from 5 to 19 in 2 second intervals and provides feedback and an opportunity to a premature termination, call from the main thread:
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure MyThreadProgress(Value: Integer; Proceed: Boolean);
end;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
RunMyThread(5, MyThreadProgress);
end;
procedure TForm1.MyThreadProgress(Value: Integer; Proceed: Boolean);
begin
Caption := IntToStr(Value);
end;
This thread automatically kills itself on either thread's or application's termination.
Maybe this unit is a little overkill for your situation because it is capable of handling multiple threads (of the same type), but I think it answers your question. Adjust to your liking.
Partial origin of this answer: NLDelphi.com.
ibdac query ( http://www.devart.com/ibdac/components.html ) has a function executing where I can write something like:
while MyQuery.Executing do
begin
application.ProcessMessages;
Sleep(1);
end;
how do I implement the same code with a dbexpress query (there is no similar function)?
There is no similar functionality. But you can execute MyQuery in a background thread and main thread will wait when the background thread is finished. For example:
type
TMyThread = class(TThread)
private
FQuery: TSQLQuery;
protected
procedure Execute; override;
public
constructor Create(AQuery: TSQLQuery);
end;
constructor TMyThread.Create(AQuery: TSQLQuery);
begin
inherited Create;
FreeOnTerminate := False;
FQuery := AQuery;
end;
procedure TMyThread.Execute;
begin
FQuery.ExecSQL;
end;
var
oThread: TMyThread;
....
oThread := TMyThread.Create(MyQuery);
try
while not oThread.Finished do begin
Application.ProcessMessages;
Sleep(1);
end;
finally
oThread.Free;
end;
PS: Btw, i am using AnyDAC. It has build-in background execution.