I am using TIdHttp and TXMLDocument inside a thread in a Delphi program.
Now I want to know:
Do these classes use COM objects so I need to call CoInitialize and CoUninitialize in this thread?
If yes, do I have to use these functions at the body of execute method or at all methods that use TIdHttp or TXMLDocument classes?
TIdHTTP has no COM dependency.
TXMLDocument can have a dependency on COM. On Windows, out of the box it is a wrapper around Microsoft's MSXML ActiveX component, which uses COM. If you use another DOM vendor (for example, OmniXML, available from XE7) then there is no COM dependency. You can control this by setting the DefaultDOMVendor global variable.
CoInitialize and CoUninitialize must be called once from within the thread context. Typically in the Execute() method of TThread, as seen in this example flow:
procedure TMyThread.Execute;
begin
try
CoInitialize(nil);
try
while not Terminated do
begin
DoWorkThatMayUseCOM;
end;
finally
CoUninitialize();
end;
except
on E: Exception do
// log exception
Log(E);
end;
end;
Related
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.
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.
I have a problem about adding string to Tmemo using TThread.ShowMessage can Show this string.The Application doesn't give any error about adding string to Tmemo but It doesn't be added to TMemo.So here is my code:
procedure TThreadGet.Execute;
var
Filed:TStringList;
begin
Filed:=TStringList.Create;
Filed.LoadFromFile(Username1+'.dat');
Messaged:=Touser+':'+Filed.Text;
Form2.Memo1.Lines.Add(Messaged);//Doesn't give error.But Doesn't Add String.
Showmessage(Messaged);//Shows String Right.
end;
Filed.Free;
Terminate;
end;
All access to VCL components must be performed in the main UI thread. For instance you may use TThread.Synchronize or TThread.Queue to arrange this.
The main reason for this is that Win32 windows have thread affinity. They also are only safe to access from the thread that created them. This property gives a very strong push to single thread UI and the VCL design goes this way.
Multi-threaded UI is possible in Win32 although it is much more tricky to do correctly. The VCL does not support that at all.
I have an activex component being created with the threading model "both" on delphi. It works perfectly, until I execute a stress test which create 50 or more threads and starts creating the activex on each thread. In this scenario after some time of perfect execution, an Access Violation error occurs on the creation of the component, inside AxCmps.TActivexComponentControl.Create, without even reaching my component initialization code. The specific point where the exception occurs is on TWinControl.Create.
Does anyone know if this is a bug, or if I am doing wrong by having multiple threads create an instance of a component with "both" threading model?
Edit: the component isnt visual (means it is an invisible active x)
Edit2: If I wrap the create and free of the component with a critical section, then the problem doesnt occur
Thread code:
Coinitialize(nil);
try
for _j := 1 to LOOPS do
begin
try
CS.Enter;
_comp := MyComp.Create(nil);
CS.Leave;
try
CallMethods; //not synchronized
finally
CS.Enter;
_comp.Free;
CS.Leave;
end;
except
on E: Exception do
LogErrors(E);
end;
end;
finally
Couninitialize;
end;
After changing the implementation of my component from TActiveXComponent to TAutoObject and changing the corresponding factory, the access violation stopped occurring in my tests. Instead of using the automatically generated wrapper class TMyComponent.Create, I called CoMyComponent.Create. The only problem is, I cannot hook events through the interface.
I have some inherited code for opening IE and I have some troubles, here is what I have:
IEUnit.pas (no form) has routines for LoadIE and OpenIE
OpenIE is called from separate thread and it looks like this
procedure OpenIE(URL: OleVariant; FieldValues: string = '');
var ie : IWebBrowser2; // <-- This should become "global" variable for the IEUnit
begin
ie := CreateOleObject('InternetExplorer.Application') as IWebBrowser2;
ie.Navigate2(URL, Flags, TargetFrameName, PostData, Headers);
ShowWindow(ie.HWND, SW_SHOWMAXIMIZED);
ie.Visible := true;
...
end;
I would like to have "global" ie variable in the unit and to write LoadIE routine like this :
LoadIE should be called from FormCreate (main thread)
It should just create "global" ie object
ie := CreateOleObject('InternetExplorer.Application') as IWebBrowser2;
so the OpenIE function doesn't need to create it, just to use it (purpose is to speed things up)
So problem is how to access same OLE object from 2 different threads, one creates the object, the other one uses it.
When I write code that doesn't take care of threads I get an error
exception class EOleSysError with message 'The application called an interface that was marshalled for a different thread'
How should I do it, to take care of threads (I'm not experienced with threads, some reading and video links are welcome).
Thanks in advance
MTA model COM servers can only be used from within their associated apartment. Here's the explanation of the error with the following advice:
The correct way of transferring an interface pointer (either a direct
pointer or a proxy pointer) from one apartment to another is via COM's
marshaling mechanism. The source apartment can call
CoMarshalInterThreadInterfaceInStream() to marshal the interface
pointer to a shared (global) stream. The destination apartment can
unmarshal this interface pointer by calling
CoGetInterfaceAndReleaseStream().
As you know, because it was the subject of your previous question, you need all calls to the COM object to be made from the same thread. The obvious choice is the main GUI thread. So, create the IWebBrowser2 in your main form's OnCreate event handler. And then use TThread.Synchronize or TThread.Queue whenever you need to show the browser. The code that you pass to Synchronize or Queue will be executed on the main GUI thread.
Assuming you are using a modern version of Delphi with support for anonymous methods you'd write it like this:
procedure TMyThread.ShowBrowser(const URL: string);
var
Proc: TThreadProcedure;
begin
Proc := procedure
begin
MainForm.Browser.Navigate2(URL, ...);
ShowWindow(MainForm.Browser.HWND, SW_SHOWMAXIMIZED);
MainForm.Browser.Visible := true;
end;
Queue(Proc);
end;
Non-free-threaded COM objects can only be used by/on/in the same thread as the one it's created for/on/in/with. In your case, to speed things up, I would use a plain global treadvar value of type IWebBrowser2, or a property in your class overriding TThread.
use CriticalSection to wrap all calls to the OleObject. also use Synchronize to call from the "other" thread to the main UI thread (this is why you get the exception).