Creating an object (with a timer) inside a TidHTTPServer.OnCommandGet fails [closed] - multithreading

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
Inside a TidHTTPServer.OnCommandGet I create a new object. This Object has a timer that should start immediately but doesnt. The TimerEVent never fires!
When I create the object somewhere else it works...
Some code
TVolumeFader=Class(TObject)
...
constructor TVolumeFader.Create(...);
begin
inherited Create;
...
VolTimer:=TTimer.Create(NIL);
VolTimer.Enabled:=FALSE;
VolTimer.Interval:=100;
VolTimer.OnTimer:=DoTimerTick;
end;
procedure TVolumeFader.DoTimerTick(Sender:TObject);
begin
LogWrite('TimerTick in VolumeFader',Debug);
If Assigned(VolTimer)then Begin;
VolTimer.Enabled:=FALSE;
End;
try
LogWrite('Executing VolumeFade in VolumeFader',Debug);
VolumeFade;
finally
If Assigned(VolTimer)then
VolTimer.Enabled:=TRUE;
end;
end;
procedure TMain.OnCommandGet;
Begin;
TVolumeFader.Create(...);
End;

In your object's constructor, you are creating a TTimer ok, but you are setting its Enabled property to False. So make sure you actually activate the timer once the constructor has exited. Or else change False to True in the constructor.
That being said, your code still won't work as shown. This is because TIdHTTPServer is a multi-threaded component, its OnCommand... events are fired in the context of worker threads that TIdHTTPServer creates for itself when clients connect to the server. But TTimer is a message-based timer, it creates an internal HWND for itself which is tied to the thread that it is created in, and that thread must have a message loop in order for TTimer to process WM_TIMER messages. The worker thread that you are creating your TTimer in does not have a message loop, so the TTimer will not be able to fire its OnTimer event.
So, you will have to either:
run your own message loop inside of the OnCommand... event handler after creating your object, and then free the object before the event handler exits. There is no guarantee that the calling thread will continue running once the OnCommand... event handler has exited. Just note that the client will be blocked from sending any further HTTP commands to the server while the timer is running:
procedure TMain.OnCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
fader: TVolumeFader;
msg: tagMSG;
begin
...
fader := TVolumeFader.Create(...);
try
while (timer should keep running) do
begin
//Application.ProcessMessages;
if PeekMessage(#msg, 0, 0, 0, PM_REMOVE) then
begin
TranslateMessage(#msg);
DispatchMessage(#msg);
end else
Sleep(100);
end;
finally
fader.Free;
end;
...
end;
delegate the creation of your object, and thus its TTimer, to your main UI thread, so that your OnTimer event handler will be fired in the context of that thread rather than in the context of the server's worker thread. Just make sure that your OnTimer code is thread-safe, if it needs to access anything that is shared with the server:
procedure TMain.OnCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
...
TThread.Synchronize(nil, // or TThread.Queue()
procedure
begin
TVolumeFader.Create(...); // when do you destroy this object?
end
);
...
end;

Related

Delphi: How do I make VCL controls thread-safe when interacting with data from another thread?

In a unit DeviceManager, I have the VCL form formDeviceManager that interacts with data from another TThread, DetectDrivesThread. DetectDrivesThread is created and executed in the main form of the program.
procedure TDetectDrivesThread.Execute;
begin
while not Terminated do
begin
sleep(1000);
try
DetectDrives.refreshRemovableDrivesList(formMain.dbConn);
finally
end;
end;
end;
This is the constructor for TDetectDrivesThread:
constructor TDetectDrivesThread.Create();
begin
inherited Create(false);
DetectDrives := TDetectDrives.Create(formMain.dbConn);
FreeOnTerminate := false;
end;
formDeviceManager has several controls that allows the user to view, remove and change data about such drives. However, my controls are not thread-safe and while DetectDrivesThread is executing , I could use the "Remove Device" button in formDeviceManager to Free the object corresponding to the USB Device. This can cause the program to crash if any methods from that object are running.

Correct logging within thread without dialog with Eurekalog

I have a Delphi 10 project using the latest version of EurekaLog. I'm currently using EurekaLog to help me debug problems in my production clients.
I noticed that EurekaLog wasn't registering errors that happened within threads. After I started reading up on it, I found that I need to change from TThread to TThreadEx, and add the following code at the start of my Execute overriden method.
SetEurekaLogStateInThread(ThreadID, true);
Despite this, when an error happens, it does not generate an event in the EL file.
If I add ExceptionManager.StandardEurekaError('TThrdSincArquivos.Execute => ' + ex.Message); on the try..except, it does log. But the stack trace is displayed as if the error occurred on the line where I call StandardEurekaLog(), not on the line where the error actually occurred. This defeats the purpose of the whole thing.
Another problem is that it displays a dialog box, which I don't want, since the error occurred inside a background thread. I just want it logged. I should get a dialog only with errors on the main thread.
How can I achieve theses results within the thread?
Actually log the error with the correct stack.
When on the main thread, display the dialog, but within a thread, just log with no dialog.
EDIT
Below is my EurekaLog Muti-threading configuration
Here is my thread declaration:
unit ThrdSincArquivos;
interface
uses
System.Classes, System.SysUtils, System.Generics.Collections, REST.Client, REST.Types,
System.JSON, Data.DB, Datasnap.DBClient, FireDAC.Comp.Client, FireDAC.Stan.Param, System.SyncObjs, EBase, EExceptionManager, EClasses;
type
TThrdSincArquivos = class(TThreadEx)
private
My thread's Create
constructor TThrdSincArquivos.Create(pPrimeiraExec: boolean; tipoSincParam: TTipoSinc);
begin
inherited Create(true);
NameThreadForDebugging('TThrdSincArquivos');
primeiraExec := pPrimeiraExec;
tipoSinc := tipoSincParam;
executadoThreadSinc := false;
FreeOnTerminate := true
end;
The start of my Execute
procedure TThrdSincArquivos.Execute;
var
contador: Integer;
begin
inherited;
try
and the end of the Execute
except
on ex: Exception do
begin
oLog.GravarLog(ex, 'TThrdSincArquivos.Execute => FIM');
end;
end;
end;
It refuses to log any exception to the Elf file. I tried to add a raise after my own log routine, but it still didn't help. It should log, but it isn't, unless I explicitly call the StandardEurekaError, but I get the stack wrong, and I get the dialog.
When you are using TThread class - it saves thread exception to .FatalException property, which you are supposed to handle in some way. Either from thread event, or from other (caller) thread. EurekaLog does not break this behaviour. E.g. your previosly written code will not change its behaviour when you enable EurekaLog. That way your properly written code would work correctly both with and without EurekaLog.
How your code is currently handling thread exceptions? Are you doing something like ShowMessage or custom logging? This obviosly would not work with EurekaLog, it does not know that you are processing exceptions with ShowMessage or your own custom logging code. You probably want something like Application.ShowException or re-raise in caller thread.
If you can not use default RTL/VCL processing (which is hooked by EurekaLog) for some reason - then you need to tell EurekaLog that you want to handle this particular exception. For example, from docs: you can use (for example) HandleException(E); from EBase unit:
Thread.WaitFor;
if Assigned(Thread.FatalException) then
begin
// Your old code is here
// Do your own thing: show message, log, etc.
// Tell EurekaLog to do its thing:
HandleException(Thread.FatalException);
end;
You would probably want to set exception filter or use events to disable dialogs for thread exceptions, because presumably you have already processed exception yourself (e.g. already showed message).
There is A LOT more ways to handle exception in threads, and EurekaLog's docs illustrate each thread case (like BeginThread, TThread, thread pools, etc.) with several possible options. It is just not reasonable to pack all this information into a single answer.
If, for some reason, you do not have code that processes .FatalException property of TThread - then you can use TThreadEx class and its .AutoHandleException property to handle exceptions automatically when thread exits, as described here:
type
TMyThread = class(TThreadEx)
protected
procedure Execute; override;
end;
procedure TMyThread.Execute;
begin
// ... your code ...
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Thread: TMyThread;
begin
Thread := TMyThread.Create(True, 'My thread');
Thread.AutoHandleException := True; // <- added
Thread.FreeOnTerminate := True;
Thread.Start;
Thread := nil; // never access thread var with FreeOnTerminate after Start
end;
However, be aware that you code will not work properly (e.g. will ignore exceptions) if you decide to disable EurekaLog in the future. Because if you remove EurekaLog from your project - then your project will have no code to handle thread exceptions!
P.S.
I need to change from TThread to TThreadEx, and add the following
code at the start of my Execute overriden method.
SetEurekaLogStateInThread(ThreadID, true);
That is slightly incorrect: you can do either one or another, but not both. And there are other ways to tell EurekaLog that it should hook exceptions in this thread.
Basically, exception life has two stages: raise and handle. EurekaLog hooks both stages when they are implemented in default RTL/VCL code. You need to explicitly indicate which threads you want to hook, because you probably want to ignore system / 3rd party threads, which you have no control over. And it so happens that default processing for TThread does not exist in RTL/VCL. That is why there is nothing to hook.

Simple multithreading Delphi

I am still quite new to threading. I want to create a procedure which tests for a valid internet connection while the main thread creates the necessary forms. The code snippet stops at the end of the constructor with a 'Cannot call Start on a running or suspended thread' error. And for some reason the main form is closed after this error
constructor TPingThread.Create(IDThread: Integer);
begin
Self.FID:=IDThread;
Self.FreeOnTerminate:=true;
end;
destructor TPingThread.Destroy;
begin
EndThread(FID);
inherited;
end;
procedure TPingThread.Execute;
var
iTimeOuts, K: Byte;
sWebpage: String;
begin
inherited;
iTimeOuts:=0;
FIdPing:=TIdHTTP.Create(nil);
for k:=1 to 3 do
begin
Try
FIdPing.ConnectTimeout:=2000;
sWebpage:=FIdPing.Get('http://www.google.co.za')
Except
On Exception do inc(iTimeOuts);
End;
end;
if iTimeOuts=3 then MessageDlg('A working internetconnection is needed to reset your password',mtWarning,[mbOK],0);
if iTimeOuts=0 then FInternetConnection:=false
else FInternetConnection:=true;
FreeAndNil(FIdPing);
end;
There are some problems with your code:
You need to call the inherited constructor:
constructor TPingThread.Create(IDThread: Integer);
begin
inherited Create(false); // Or true to create a suspended thread
Self.FID:=IDThread;
...
Remove the inherited call in the Execute method, since this is an abstract declaration of TThread. Not an error per se, but should be avoided for clarity.
Use a try/finally after creating FIdPing in the Execute method.
As #mjn says, there is no need to call EndThread(), since the TThread handles this for you.
Calling the VCL MessageDlg() from a thread is not thread safe. You need to synchronize the call or use the Application.MessageBox, a Delphi wrapper to windows MessageBox. Best solution would be to skip the dialog and pass an error message to the main thread, which would need to know this error anyway.

AsyncCall with Delphi 2007

What I basically want is to start AsyncCall and proceed with my code loading. I have Interface section that consumes lots of time (600+ms) and I want to load this code in independent thread.
I've tried to use AsyncCall to make something like this:
procedure Load;
begin
...
end;
initialization
AsyncCall(#Load, []); // or LocalAsyncCall(#Load)
However, this Load procedure actually starts in Main thread and not in the new created thread. How can I force the Load procedure to be loaded in any thread other than MainThread?
I can create TThread and Execute this but I want to force AsyncCall or LocalAsyncCall or anything from AsyncCall library to make to work.
Thanks for your help.
Have you tried something like this?:
procedure Load;
begin
if GetCurrentThreadId <> MainThreadID then
Beep;
end;
var a: IAsyncCall;
initialization
a := AsyncCall(#Load, []);
a.ForceDifferentThread;
ForceDifferentThread() tells AsyncCalls that the assigned function must
not be executed in the current thread.
The problem is that your code is not retaining the IAsyncCall interface that is returned by the AsyncCall function.
AsyncCall(#Load, []);
//AsyncCall returns an IAsyncCall interface,
//but this code does not take a reference to it
Because of this, the interface that is returned has its reference count decremented to zero as soon as the initialization section completes. This therefore frees the object that implements the interface which does this:
destructor TAsyncCall.Destroy;
begin
if FCall <> nil then
begin
try
--> FCall.Sync; // throw raised exceptions here
finally
FCall.Free;
end;
end;
inherited Destroy;
end;
The key line is the call to Sync which forces the asynchronous call to be executed to completion. All this happens in the main thread which explains the behaviour that you report.
The solution is that you simply need to keep the IAsyncCall interface alive by storing it in a variable.
var
a: IAsyncCall;
initialization
a := AsyncCall(#Load, []);
In the real code you need to ensure that Load had completed before running any code that is reliant on Load. When your program reached a point where it required Load to have been called it has to call Sync on the IAsyncCall interface.
So you might write it something like this.
unit MyUnit;
interface
procedure EnsureLoaded;
implementation
uses
AsyncCalls;
....
procedure Load;
begin
....
end;
var
LoadAsyncCall: IAsyncCall;
procedure EnsureLoaded;
begin
LoadAsyncCall := nil;//this will effect a call to Sync
end;
initialization
LoadAsyncCall := AsyncCall(#Load, []);
end.
The call EnsureLoaded from other units that required Load to have run. Or, alternatively, call EnsureLoaded from any methods exported by MyUnit that depended on Load having run. The latter option has much better encapsulation.

Exist any drawback if i use a callback function inside of a thread to comunicate with the main thread of my app?

i wrote a Thread.descendent class, and to comunicate my thread with the main thread i use a callback function so i am wondering if is a valid solution or instead i must use windows messages?
type
TMyCallBack= procedure(const Param1,Param2: string) of object;
TMyThread= class(TThread)
private
P1 : string;
P2 : string;
MyCallBack : TMyCallBack;
procedure Process;
public
Constructor Create(CallBack : TMyCallBack); overload;
destructor Destroy; override;
procedure Execute; override;
end;
procedure TMyThread.Process;
begin
FCallBack(P1,P2);
end;
constructor TMyThread.Create(CallBack : TMyCallBack);
begin
inherited Create(False);
FreeOnTerminate := True;
MyCallBack := CallBack;
end;
procedure TMyThread.Execute;
begin
while True and not Terminated do
begin
AResult:= FListener.GetResult(Param1,Param2,5000);
if not VarIsNull(AResult) then
begin
P1:=AResult.Value1;
P2:=AResult.Value2;
Synchronize(Process);
end;
end;
end;
As long as you use Synchronize you should be fine.
If you run the callback via Synchronize, it's OK, as most Delphi implementations:
create a callback structure, containing the callback and an event handle
append the callback structure to a locked global list
post a message to the main thread, to wake it from WaitMessage or alike
wait on the event until the callback completes
This may or may not be better than using raw window messages, as:
the callback list is checked in clearly defined places and as such its not as much eligible for reentrancy issues
for the same reasons, its certainly a bit less performant
it may cause problems with modal windows and native popup menus, which allow sent message processing, but may bypass the synchronization list handling in some cases
As long as the callback processing must mot be waitable/cancellable, and you can tell for sure it doesn't do anything that might cause sent message processing (as most windows-message-related routines do!), you may prefer using SendMessage, with appropriate parameter marshaling.

Resources