There is a multi-threaded app, which runs 24/7. A correct disposal of resources as well as a proper exceptions handling (including EAccessViolation) are key factors.
I am a little bit stuck on understanding how to correctly nest exception-handling blocks within a thread functions.
Among TMyThread.Execute there are two helper-function:
function LoadHtml(const AUrl: sting): string - a simple wrapper for TIdHTTP.Get()
function ParsePage(const Id: string): TOffers - a parser/DB-updater function
Execute starts querying database for the initial recordset of IDs. Then it starts a while not rs.Eof do loop where calls ParsePage which is a main processor.
ParsePage loads HTML (care of LoadHtml), then performs some string parsing operations and finally updates database.
Here is a code structure: (details omitted for brevity)
{Wrapper-function to load HTML page}
function TMyThread.LoadHtml(const AUrl: string): string;
var
Response: TStringStream;
HTTP: TIdHTTP;
begin
Result := '';
Response := TStringStream.Create('');
try
try
HTTP := TIdHTTP.Create(nil);
HTTP.ReadTimeout := 10000;
HTTP.Response.KeepAlive := false;
try
HTTP.Get(AUrl, Response);
if HTTP.ResponseCode = 200 then Result := Response.DataString;
finally
HTTP.Free;
end;
finally
Response.Free;
end;
except
//This code will run only on exception and *after* freeing all resources?
on E: EIdHTTPProtocolException do
if E.ErrorCode = 404 then
raise EMyOwnHTTPNotFoundError.Create('Page not found');
else
HandleErrorAndLogItToDB(E.Class);
end;
end;
{Loads HTML, processes it and updates DB}
function TMyThread.ParsePage(const Id: string): TOffers;
var
RawHTML: string;
Offer: TOffer; //a simple record to store key offer details;
begin
Result := TOffers.Create;
try {top-level try..except block}
try {Critical request. If it fails I want to move}
RawHTML := LoadHtml('http://onlinetrade.com/offer.html?id=' + Id);
except
on E: EMyOwnHTTPNotFoundError do {Defined in function LoadHtml()}
//Update DB: product does not exist.
else
HandleErrorAndLogItToDB(E.Class);
end;
end;
try
//Preform some basing string operations on RawHTML
except
on E: Exception do HandleErrorAndLogItToDB(E.Class);
end;
try {Iterate through some blocks of data and put them in the Offers: TList}
for i := 0 to N do
begin
//Set up TOffer record
Result.Add(Offer);
end
finally
FreeAndNil(Offer);
end;
except
on E: Exception do
begin
HandleErrorAndLogItToDB(E.Class);
FreeAndNil(Result);
raise; {does this return control to Execute?}
end;
end;
end;
Now Execute:
procedure TMyThread.Execute;
var
j: Integer;
s: string;
Offers: TOffers; {Is a simple TList to store a collection of TOffer (record)}
begin
inherited;
CoInitialize(nil); {ADO is in da house}
try {top-level try..except block}
try {nested try..finally to call CoUninitialize}
try {A critical operation which sources all further operations}
rs := AdoQuery('GetSomeRecords ' + IntToStr(SomeId));
except
on E: Exception do
begin
HandleErrorAndLogItToDB(E.Class);
Exit; {DB-query error means no reason to continue}
end;
end;
while not rs.EOF do
begin
try //a loop top-level try..except handler
Offers := ParsePage(rs.Fields['Id'].Value);
try //nested resource freeer
begin
try //nested try..except to handle DB queries
for j := 0 to N do with Offers.Items[j] do
AdoUpdateDB; //Update DB
Synchronize(UpdateProgressBar);
except
on E: Exception do
begin
HandleErrorAndLogItToDB(E.Class);
Continue; //as suggested
raise; //as suggested
end;
end;
rs.MoveNext;
end;
finally
FreeAndNil(Offers);
end;
except
on E: Exception do HandleErrorAndLogItToDB(E.Class);
end;
end; //end while..do loop
Synchronize(ResetProgressBar);
finally
CoUnitialize;
end;
except
on E: Exception do
begin
//Make everything possible to keep the thread running. No matter of:
//- HTTP/404 - Not Found exceptions (which I handle)
//- UpdateDatabase fails
//- String operation exceptions
//If anything critical occurs, Execute() shall just go to the next offer
//even if the current one is not properly processed.
end;
end;
Looking at this code I think, I try to handle too many exceptions which I probably do not need to handle, just passing them to the most outer try..except handler in Execute. There are just a couple of exceptions I really need to handle: initial database query and EMyOwnHTTPNotFoundError (to set a flag that the offer doesn't exist). I read somewhere a suggestion not to explicitly chase for exception handling unless you really need it...
However it is very important that the thread keeps on running no matter of which exceptions are thrown inside/outside any code blocks. The idea is to completely ignore exceptions and never break either while..do loop or stop the thread. At the same time, correctly disposing resources is also a must.
I would be grateful for any suggestions/comments on how to improve this code.
There is no good way to "handle" an access violation, or really any exception that does not indicate a specific condition that your code has already planned for. (For example, a File Not Found exception can be handled in a good way if you can simply tell the user to ask for another file.)
If an exception is raised as the result of a bug, it means something is happening in your code that you did not plan for. Your code rests on a bunch of assumptions about things going right, things working as planned, and when an unexpected exception is raised, it means that those assumptions no longer necessarily hold. The best thing to do at that point is produce an error report to send back to you, and then shut down as quickly as possible.
Why? Because one of your assumptions that may no longer hold is that "critical data is in a valid, non-corrupted state." If the program keeps going, blindly following the assumption that all its data is good and then acting on it, it can turn a small problem into a much bigger one very, very quickly.
I completely understand the desire to make the program keep on going no matter what, but unfortunately it fundamentally conflicts with reality. The only sane thing to do when you get an unhandled exception--especially something like an access violation that can only be the result of buggy code of some variety--is to produce an error report and shut down.
If downtime is a Very Bad Thing, you could have something in place to make sure to start back up again as quickly as possible. This will keep you running, but it will reset your invariants (fundamental assumptions) and clear out the corrupt data. But for the love of all that is binary, shut the program down, and do it right away.
Then take the error report and fix your bug.
Related
Some background:
Basically it comes down to that I want to be able to "execute" the task in the current thread.
Why? -I have a task creator routine, and one time I want the task to be executed immediately in a background task, and at other times I want the task to be scheduled using an IOmniThreadPool.
In the case that I want to use the OmniThreadpool I want do do some output about the task's status, like that it was queued, started, finished or failed.
The task's execution is not yet started at this moment.
So I imagined someting like this:
function WrapIntoPoolTask(const aOriginalTask:IOmniTaskCOntrol):IOmniTaskCOntrol;
begin
if Assigned(aOriginalTask) then
begin
var lPoolProgress:=TaskPoolProgress; // fetch global task pool progress interface
// immediately displays message says its been queued, remember message bookmark
var lMessageBookMark:=lPoolProgress.MessageList.AddMessage(aOriginalTask.Name,pmPaused);
Result:=CreateTask(
procedure (const aTask:IOmniTask)
begin
lPoolProgress.MessageList.UpdateMessage(lMessageBookMark,pmStarted); // update message status
try
aOriginalTask.ExecuteTaskInThisThread; // <<=== So how do I do this?
lPoolProgress.MessageList.UpdateMessage(lMessageBookMark,pmCompleted);
except
lPoolProgress.MessageList.UpdateMessage(lMessageBookMark,pmFailed);
raise;
end;
end
,
'Pooled:'+aOriginalTask.Name
);
end;
end;
Using the UpdateMessage call after performing the original task can be moved to the OnTerminated handler of the IOmniTaskControl interface. I tried that and it works just fine for the thread ending part. It even allows for handling exit codes and exit messages which I like even better.
I think what I am missing here is probably an OnInitialize or OnStartExecution handler to set my pmStarted status.
Question:
How can I directly execute a tasks "body" from IOmniTaskCOntrol
or
how can I add some initialization code to my task after the task was already created. This code should be executed immediatliy prior to executing my tasks original body.
In order to solve my problem I had to change the omnithreadlibrary unit OtlTaskControl a bit.
one routine added to IOmniTaskControl (GUID should change, but I didn'tt)
IOmniTaskControl = interface ['{881E94CB-8C36-4CE7-9B31-C24FD8A07555}']
...
function DirectExecute:IOmniTaskControl;
...
end; { IOmniTaskControl }
And the implementation added to TOmniTaskControl:
function TOmniTaskControl.DirectExecute:IOmniTaskControl;
VAR lTask:IOmniTask;
begin
Result:=self;
lTask:=CreateTask;
(lTask as IOmniTaskExecutor).Execute;
end;
Then my "custom wrapper" routine that actually adds the pool progress handling to whatever the original task was:
function WrapIntoOmniPoolTask(const aTaskControl:IOmniTaskCOntrol):IOmniTaskCOntrol;
var lTaskControl:IOmniTaskCOntrol;
lPoolProgress:IAppProgress;
lmbm:integer;
begin
if Assigned(aTaskControl) then
begin
// have some local copies to work around compiler bug RX10.3 and RX10.4
// cannot use inline vars due to this bug either.
lTaskControl:=aTaskControl;
lPoolProgress:=TaskPoolProgress;
lmbm:=lPoolProgress.MessageList.AddMessage(aTaskControl.Name,pmPaused);
Result:=CreateTask(
procedure (const aTask:IOmniTask)
begin
try
lPoolProgress.MessageList.UpdateMessage(lmbm,pmStarted);
try
lTaskControl.DirectExecute;
aTask.SetExitStatus(lTaskControl.ExitCode,lTaskControl.ExitMessage);
HandlePoolTaskTermination(lTaskControl,lmbm,lPoolProgress);
except
HandlePoolTaskTermination(lTaskControl,lmbm,lPoolProgress);
if IsFatalException then
raise;
end;
finally
// release interfaces when all is done
lPoolProgress:=nil;
lTaskControl:=nil;
end;
end,
'Pooled: '+lTaskCOntrol.Name
);
end;
end;
And finally, the routine that schedules my wrapped task into the omnipoolthread.
function TfrmMain.CreateTestTask:IOmniTaskControl;
begin
Result:=WrapIntoOmniPoolTask(CreateTask(TestTask,TGUID.NewGuid.ToString)).Unobserved;
end;
Everything seems to work as expected including the exit code and exit message which are propagated from the inner task to the outer task.
The compiler bug I am referring to is reported here: https://quality.embarcadero.com/browse/RSP-29564 (please vote!)
For those interested: this is what HandlePoolTaskTermination looks like:
procedure HandlePoolTaskTermination(const aTaskControl:IOmniTaskCOntrol;const aMessageBookmark:integer;const aPoolProgress:IAppProgress);
begin
var pm:=pmCompleted;
if Assigned(aTaskControl.FatalException) then
begin
pm:=pmWarning;
var pe:=peError;
if IsAbortException(aTaskControl.FatalException) then
pe:=peWarning
else if IsFatalException(aTaskControl.FatalException) then
begin
pm:=pmFailed;
pe:=peFatalError;
end;
aPoolProgress.ErrorList.AddErrorToMessage(aMessageBookmark,'',pe,aTaskControl.FatalException)
end
else if aTaskControl.ExitCode<>0 then
begin
pm:=pmWarning;
aPoolProgress.ErrorList.AddErrorToMessage(aMessageBookmark,aTaskControl.ExitMessage,peWarning);
end;
aPoolProgress.MessageList.UpdateMessage(aMessageBookmark,pm);
end;
The IsFatalException returns true if the "current" exception is eg EAccessViolation, EInvalidOperation and alike.
I'm starting a project for barcode reading in a FMX/Android app (new to barcode reading, I've some experience with FMX).
I found this open-source ZXing.Delphi library (greatly helpful) and I went through the code of this example.
I may not be used enough to TThread but I'm wondering on the use of TThread.Synchronize in this case, because I have never seen/used it like this before.
1 - The function responsible for capturing + scanning an image in search of a barcode is GetImage. It is sync'd to main thread on CameraComponent1SampleBufferReady.
procedure TMainForm.CameraComponent1SampleBufferReady(Sender: TObject;
const ATime: TMediaTime);
begin
TThread.Synchronize(TThread.CurrentThread, GetImage);
end;
2 - GetImage function contains TTask.Run which again makes use of TThread.Synchronize to sync to the main thread.
procedure TMainForm.GetImage;
var scanBitmap: TBitmap; ReadResult: TReadResult;
begin
CameraComponent1.SampleBufferToBitmap(imgCamera.Bitmap, True);
...
scanBitmap := TBitmap.Create();
scanBitmap.Assign(imgCamera.Bitmap);
ReadResult := nil;
// There is bug in Delphi Berlin 10.1 update 2 which causes the TTask and
// the TThread.Synchronize to cause exceptions.
// See: https://quality.embarcadero.com/browse/RSP-16377
TTask.Run(
procedure
begin
try
FScanInProgress := True;
try
ReadResult := FScanManager.Scan(scanBitmap);
except
on E: Exception do
begin
TThread.Synchronize(nil,
procedure
begin
lblScanStatus.Text := E.Message;
end);
exit;
end;
end;
TThread.Synchronize(nil,
procedure
begin
...
if (ReadResult <> nil) then
begin
Memo1.Lines.Insert(0, ReadResult.Text);
end;
end);
finally
ReadResult.Free;
scanBitmap.Free;
FScanInProgress := false;
end;
end);
end;
? : Is it usual/good pratice to encapsulate TThread.Synchronize( TTask.Run( TThread.Synchronize(...) ) ); ?
? : Couldn't it be the cause of the mentionned exceptions encountered in Delphi 10.1 update 2 ?
When I "learned" to use Parallel Programming Library, I mainly used :
this entry on Emb'ro
Malcolm Groves' blog
this CodeRage 9 video
Did I missed it somewhere ?
A quick update. With Embarcadero 10.3.1:
TThread.Synchronize(TThread.CurrentThread, GetImage);
generates after a random number of seconds a Segmentation Fault.
You can notice a many "Switch To Thread" calls suring debug and it will always crash the APP.
No matter if you leave GetImage function empty.
I've tried all the possibile ways but my app and also the original app crashes.
I've also tried creating an Asyincronous thread as well as Labda... same results all the times
It looks like TThread Synchronization has issues on 10.3.1. Other users reported to have it working fine on previous Tokyo version.
Any feedback from your side?
I am trying to implement a Pipeline pattern in my test project (How to make a Mutlithreded idhttp calls to do work on a StringList), but am having a struggle adapting TThread code to Pipeline pattern code. There are not many resources about how to use it.
I tried my best below, please DO NOT downvote, I know my code is messy but I'll edit my question if needed.
type
TForm2 = class(TForm)
...
private
procedure Retriever(const input: TOmniValue; var output: TOmniValue);
procedure Inserter(const input, output: IOmniBlockingCollection);
function HttpGet(url: string; var page: string): boolean;
end;
procedure TForm2.startButton1Click(Sender: TObject);
var
pipeline: IOmniPipeline;
i : Integer;
v : TOmniValue;
s : string;
urlList : TStringList;
begin
pipeline := Parallel.Pipeline;
pipeline.Stage(Retriever);
pipeline.Stage(Inserter).NumTasks(10);
pipeline.Run;
for s in urlList do
pipeline.Input.Add(s);
pipeline.Input.CompleteAdding;
// wait for pipeline to complete
pipeline.WaitFor(INFINITE);
end;
function TForm2.HttpGet(url: string; var page: string): boolean;
var
lHTTP: TIdHTTP;
i : integer;
X : Tstrings;
S,M,fPath : String;
begin
lHTTP := TIdHTTP.Create(nil);
X := TStringList.Create;
try
X.Text := lHTTP.Get('https://instagram.com/'+fPath);
S:= ExtractDelimitedString(X.Text);
X.Clear;
Memo2.Lines.Add(fPath+ ' : '+ M ); //how to pass the result to Inserter
finally
lHttp.Free;
end;
end;
procedure TForm2.Inserter(const input, output: IOmniBlockingCollection);
var
result : TOmniValue;
lpage : string;
begin
for result in input do begin
Memo2.Lines.Add(lpage);
FreeAndNil(lpage);
end;
// correect?
end;
procedure TForm2.Retriever(const input: TOmniValue; var output: TOmniValue);
var
pageContents: string;
begin
if HttpGet(input.AsString, pageContents) then
output := //???
end;
First of all - describe what is your specific problem. No one can stand behind your back and look at your computer and see what you are doing.
http://www.catb.org/esr/faqs/smart-questions.html#beprecise
You do imply your program misbehaves. But you do not describe how and why. And we do not know it.
As general remarks, you overuse the pipeline a bit.
all the worker procedures you pass to OTL - in your case those are Inserter and Retriever work in random threads. That means none of them should touch GUI without synchronizing - VCL is not multithreaded.
Also using TThread.Synchronize is a poor choice as I explained to you in the linked question. It makes program slow and it makes forms unreadable. To update your form use polling with fixed framerate. Do not update your form from inside OTL workers.
In other words, Inserter is not what you need. All you need from the pipeline here is its Input collection, a downloader procedure and the Output collection. Yes it is very simple task for the complex things pipelines are, that is why I mentioned two other simpler patterns before it.
You need TTimer on your form that would poll the Output collection at fixed framerate 2-3 times per second, and check that the collection is not finalized yet ( if it is - the pipeline got stopped ) and that should update GUI from a main thread.
You should not wait for a pipeline to finish inside your main VCL thread. Instead You should detach the pipeleine and let it run totally in background. Save the reference to the created pipeline into the Form's member variable so you could access its Output collection from the TTimer event and also can free the pipeline after its process run over.
You should keep that variable linked to the pipeline object until the downloading is over and set to nil (Free the objects) after that, but not before. You know about interfaces and reference-counting in Delphi, right?
For other OTL patterns like parallel-FOR read OTL docs about their .NoWait() calls.
You should make this Your form bi-modal, to have different set of enabled controls when downloading is running and when it is not. I usually do it with special Boolean property like I shown to you in the topic you linked.
Your user is not supposed to change the lists and settings while the pipeline is in progress (unless you would implement that realtime task changing, but you did not yet). This mode switcher would also be a good place to free the finished pipeline object when the switching is going from working mode to idle mode.
If you would want to play with the pipeline workers chaining, then you can put into the Input Collection not the URL strings themselves, but the array of those - the Memo1.Lines.ToArray(), then you can start with Unpacker stage that gets string arrays from the input collection (there would be only one, actually) and enumerate it and put the strings into stage-output collection.
This however has little practical value, it would even slow your program down a tiny bit, as the Memo1.Lines.ToArray() function would still work in the main VCL thread. But just to experiment with the pipelines this might be funny.
So the draft becomes like that,
TfrmMain = class(TForm)
private
var pipeline: IOmniPipeline;
property inProcess: Boolean read ... write SetInProcess;
...
end.
procedure Retriever(const input: TOmniValue; var output: TOmniValue);
var
pageContents, URL: string;
lHTTP: TIdHTTP;
begin
URL := input.AsString;
lHTTP := TIdHTTP.Create(nil);
try
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
pageContents := ExtractDelimitedString( lHTTP.Get('https://instagram.com/' + URL) );
if pageContents > '' then
Output := pageContents;
finally
lHTTP.Destroy;
end;
end;
procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if InProgress then begin
CanClose := False;
ShowMessage( 'You cannot close this window now.'^M^J+
'Wait for downloads to complete first.' );
end;
end;
procedure TfrmMain.SetInProcess(const Value: Boolean);
begin
if Value = InProcess then exit; // form already is in this mode
FInProcess := Value;
memo1.ReadOnly := Value;
StartButton.Enabled := not Value;
if Value then
Memo2.Lines.Clear;
Timer1.Delay := 500; // twice per second
Timer1.Enabled := Value;
If not Value then // for future optimisation - make immediate mode change
FlushData; // when last worker thread quits, no waiting for timer event
If not Value then
pipeline := nil; // free the pipeline object
If not Value then
ShowMessage('Work complete');
end;
procedure TfrmMain.Timer1Timer(const Sender: TObject);
begin
If not InProcess then exit;
FlushData;
if Pipeline.Output.IsFinalized then
InProcess := False;
end;
procedure TForm2.startButton1Click(Sender: TObject);
var
s : string;
urlList : TStringList;
begin
urlList := Memo1.Lines;
pipeline := Parallel.Pipeline;
pipeline.Stage(Retriever).NumTasks(10).Run;
InProcess := True; // Lock the input data GUI - user no more can edit it
for s in urlList do
pipeline.Input.Add(s);
pipeline.Input.CompleteAdding;
end;
procedure TfrmMain.FlushData;
var v: TOmniValue;
begin
if pipeline = nil then exit;
if pipeline.Output = nil then exit;
if pipeline.Output.IsFinalized then
begin
InProcess := False;
exit;
end;
Memo2.Lines.BeginUpdate;
try
while pipeline.Output.TryTake(v) do
Memo2.Lines.Add( v.AsString );
finally
Memo2.Lines.EndUpdate;
end;
// optionally - scroll output memo2 to the last line
end;
Note few details, think about them and understand the essence of those:
Only FlushData is updating the output memo. FlushData is called from the TTimer event or from the form mode property setter. Both of them only are ever called from the main VCL thread. Thus FlushData is NEVER called form background threads.
Retriever is a free standalone function, it is not a member of the form and it knows nothing about the form and has no reference to your form instance(s). That way you achieve both goals: you avoid "tight coupling" and you avoid a chance of mistakingly access the form's controls from a background thread, which is not allowed in VCL.
Retriever functions work in background threads, they do load the data, they do store the data, but they never touch the GUI. That is the idea.
Rule of thumb - all methods of the form are only called from the main VCL thread. All pipeline stage subroutines - bodies of the background threads - are declared and work outside of any VCL forms and have no access to none of those. There should be no mix between those realms.
you throttle GUI update to a fixed refresh rate. And that rate should be not too frequent. Windows GUI and user eyes should have time to catch up.
Your form operates in two clearly delineated modes - InProcess and not InProcess. In those modes different sets of functions and controls are available to the user. It also manages mode-to-mode transitions like clearing output-memo text, alerting user of status changes, freeing memory of used threads-managing objects (here: pipelines), etc. Consequently, this property only is changed (setter is called) from main VCL thread, never from background workers. And #2 helps with that too.
The possible future enhancement would be to use pipeline.OnStop event to issue a PostMessage with a custom Windows Message to your form, so it would switch the mode immediately as the work is done, not waiting for the next timer olling event. This might be the ONLY place where pipeline knows anything about the form and has any references to it. But this open the can of Windows messaging, HWND recreation and other subtle things that I do not want to put here.
I have doubt about multithreaded application(ip scanner). When i put large ip range like 192.168.0.1 to 192.168.5.1 and thread limit as 99 .So when i run my application there should be 101 threads running at a time(99 threads(ScannerChild) + Main thread + Scannerthread) and when scanning is done 99 scannerchild and 1 scannethreads would be terminated and only 1 thread should run that time(main thread). But sometimes thread count is going to 102 and after scanning thread count is not coming to 1 it shows threadcount as 2 in task manager. Whats the problem ?
code for Scannerthread
/
/Creating constructor of scannerthread
Constructor ScannerThread.Create(CreateSuspended: Boolean );
Begin
Inherited Create(CreateSuspended);
Freeonterminate:= true; //Freeonterminate is true
End;
{ScannerThread Thread }
procedure ScannerThread.Execute;
var
I : integer;
ScannerCh : array of ScannerChild; //array of ScannerChild
IpList : TStringlist; //Iplist as tstringlist
IPs: Integer; //ipcount is count of iplist
Begin
ScannerchCount:=0; //Initialising scannerchcount as 0
IpList:=TStringList.Create;//creating stringlist
IF GetNumberOfIpsInRange(Ip_From, Ip_To, IpList) Then //Function call that returns iplist if TRUE
Begin
Try
IF Assigned(LvHosts) Then //Clearing LvHosts field
LvHosts.Clear;
IPs := IpList.Count; //Ipcount is given value of iplists count
SetLength(ScannerCh, IPs); //Setting length of scannerch as ipcount
I:=0;
Repeat
While ScannerChcount > tcount-1 do //Checking if is greater than tcount(thread input) by user
Sleep(30);
ScannerCh[I]:=ScannerChild.Create(True, IpList[i]);
ScannerCh[I].FreeOnTerminate:=True;
ScannerCh[I].OnTerminate:= ScanchildTerminated; // Event scanchildterminated occurs on termination of Scannerch thread
ScannerCh[I].LvHostname := LvHosts; //Lhostname is private listview of scannechild
ScannerCh[I].Resume;
ScannerChCount:=Scannerchcount+1; //Incrementing scannerchcounts
I:=I+1;
Sleep(20); //Sleep after each thread is created so that threads will enter critical section properly
until I = IPs;
Scannerch:=nil;
If Assigned(IpList) Then //Free iplist
FreeAndNil(IpList);
Except
On E: Exception do
Begin
ShowMessage('Invalid operation :' + E.Message); //Showexception message
If Assigned(IpList) Then //Free iplist
FreeAndNil(IpList);
end;
End;
End
Else
Begin
Ipscan.lbResult.caption:='Invalid Ip Range';
Exit;
End;
Repeat //Main Thread Waiting For Ip scan Threads to finish
Sleep(100);
until ScannerChCount = 0;
End;
Scannerchild code
Constructor ScannerChild.Create(CreateSuspended: Boolean; IP: String);
Begin
Inherited Create(CreateSuspended);
//FCriticalsection := TCriticalSection.create; //Creating critical section
IPToScan:=IP;
End;
//Execution procedure for scannerchild
procedure ScannerChild.Execute;
Var
MainOutput : TListItem;//Listitem variable for adding listitems
Hostname : String; //Hostname is declared as string
Begin
Try
MainOutput:=LvHostname.Items.Add; //Adding items to mainoutput
MainOutput.Caption:=IPToScan;
Hostname := IPAddrToName(IPToScan);
If Hostname <> EmptyStr Then
Begin
MainOutput.SubItems.Add(IPAddrToName(IPToScan)); //Displaying output
End
Else
Mainoutput.subitems.add('No host');
Finally
End;
End;
//this event get called when scannerch thread terminates
procedure Scannerthread.ScanchildTerminated( Sender : TObject );
Begin
ScannerChCount:=ScannerchCount-1; // Decrementing scannerchcount
End;
There are plenty of problems here. I'm going to give you some general advice as well as try to answer your question.
Your accessing of the GUI outside of the main thread is wrong, as we've said before. No need to cover that again, please go back over your previous questions.
The design of your threading is poor. If you would ask a high level question about that we could help you fix it. I'd be happy if you asked a question that allowed me to demonstrate a simple thread pool.
As well as the problems with the threading design, you've got no separation of concerns. No modularity. The threading and the tasks and the GUI code are all mixed in with each other. You need to keep the concerns separate to make the code maintainable and well factored. If you'd only ask us how to design your program rather than to fix the bugs in your weak design we could help you.
All of the calls to Sleep and the polling are symptoms of this bad design. There should be no sleeping.
Your code has way too many comments that make it hard to read. There's no need to comment a statement like i := i+1. The effect of that is self-evident.
You need to learn how to debug threaded code. The interactive debugger is not so useful. It interferes with the timing of thread execution. Use trace logging to debug such problems. Until you learn how to do this you cannot expect to make progress. I repeat, it is critical that you learn how to debug.
As to the problem you asked about, you have a data race on the ScannerChCount variable. So the threads are probably terminating correctly but you are counting them incorrectly.
Use InterlockedIncrement and InterlockedDecrement to modify it in a thread safe manner. That is both in the child termination code and the controller thread.
You might think this is not needed because ScanChildTerminated which decrements the counter is an OnTerminate event and so executed by the main thread. But the controller thread code that increments the counter is not executed in the main thread.
If you don't yet know what a data race is, then you have started multi threaded programming too soon. Rather than my explain it I would refer you to the shared data sections of any good text book on parallel programming. Or Wikipedia: http://en.m.wikipedia.org/wiki/Race_condition.
When I am using AsyncCalls, how do you best get the exception into the main thread? For example:
procedure TUpdater.needsToGoFirst();
begin
asyncCall := TAsyncCalls.Invoke(procedure
begin
//some stuff
if (possiblyTrueCondition = True) then
raise Exception.Create('Error Message');
//more stuff
TAsyncCalls.VCLSync(procedure
begin
notifyUpdates();
end);
end);
end;
procedure TUpdater.needsToGoSecond();
begin
asyncCall2 := TAsyncCalls.Invoke(procedure
begin
//initial stuff
asyncCall.Sync();
//stuff that needs to go second
TAsyncCalls.VCLSync(procedure
begin
notifyUpdates();
end);
end);
end;
I know calling asycCall.Sync will throw the exception for me, but due the the way I'm currently having my thread notify the main thread that updates have been made, I really don't have anywhere that I'm calling Sync in the main thread. Doing so also proves difficult because I actually have another thread that is calling Sync to make sure some things are set before acquiring resources the first thread should process first.
Do I need to wrap the innards of these functions with a try-catch and use a VCLSync to get the exception to the main thread myself? Is is there a better way to check for exceptions in a main thread idle loop of some kind?
Edit 1
Another thought I had was to create a loops whose only job is to check the IAscynCall references for exceptions and use that to Raise the exception to the main thread, rather than duplicating that code in every post. The needsToGoSecond metod may still get to the exception first, but it will then hold the exception and the loop would catch it there.
You state the following:
I really don't have anywhere that I'm calling Sync in the main thread. Doing so also proves difficult because I actually have another thread that is calling Sync.
....
Do I need to wrap the innards of these functions with a try-catch and use a VCLSync to get the exception to the main thread myself? Is there a better way to check for exceptions in a main thread idle loop of some kind?
Calling Sync in the other thread will raise the exception there. If you don't want it raised there, and if it must be raised in the main thread, then there is no option. You simply have to catch any unhandled exceptions yourself, in the async procedure. You can then queue them off to the main thread, perhaps by calling TThread.Queue, posting a Windows message, or some similar queue mechanism. Another option would be to use VCLSync if you don't mind the synchronisation at that point.
The bottom line is that calling Sync from another thread, and needing the exception to be raised on the main thread, are not compatible goals. Ergo you must catch the exception yourself and stop AsyncCalls dealing with it.
Essentially this is just a broadening of your current approach. At the moment your fire notifications to the main thread, rather than have the main thread sync. After all, I guess you are using an asynchronous approach because you don't want to sync from the main thread. So, the broadening is that you need to be able to notify the main thread of errors and exceptions, as well as more normal outcomes.
As requested, here is an example of how I pass an exception inside of a thread to the main thread. An exception stops execution in the thread, and then I trap the error and return it to the user.
Here are two very simple classes, a worker class that includes the code to run in a thread, and the thread class:
type
TMyWorker = class
private
FExceptionObject: Pointer;
FExceptionAddress: Pointer;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure _Execute;
procedure GetException;
end;
implementation
constructor TMyWorker.Create;
begin
inherited Create;
FExceptionObject := nil;
FExceptionAddress := nil;
end;
procedure TMyWorker._Execute;
begin
try
// run code
except
FExceptionObject := AcquireExceptionObject; // increments exception's refcnt
FExceptionAddress := ExceptAddr;
end;
end;
procedure TMyWorker.GetException;
begin
if Assigned(FExceptionObject) then
raise Exception(FExceptionObject) at FExceptionAddress; // decrements exception's refcnt
end;
destructor TMyWorker.Destroy;
begin
if Assigned(FExceptionObject) then
begin
ReleaseExceptionObject; // decrements exception's refcnt
FExceptionObject := nil;
FExceptionAddress := nil;
end;
inherited;
end;
.
type
TMyThread = class(TThread)
private
FWorker: TMyWorker;
protected
procedure Execute; override;
public
constructor Create(Worker: TMyWorker);
end;
implementation
procedure TMyThread.Execute;
begin
FWorker._Execute;
end;
constructor TMyThread.Create(Worker: TMyWorker);
begin
FWorker := Worker;
FreeOnTerminate := False;
inherited Create(False);
end;
Then my main thread code creates a worker objects and passes it to the constructor of the thread to execute. When execution completes, the main thread checks for and re-raises any exceptions.
var
myWorker: TMyWorker;
begin
myWorker := TMyWorker.Create;
try
with TMyThread.Create(myWorker) do
begin
WaitFor; // stop execution here until the thread has finished
Free; // frees the thread object
end;
myWorker.GetException; // re-raise any exceptions that occurred while thread was running
finally
FreeAndNil(myWorker);
end;
end;
You might also look at this EDN article:
How to handle exceptions in TThread objects
So in the end, I've come up with the following solution that suits my needs, which is I simply want the GUI to show any exception message regardless of thread. If you want to manage the errors more particularly, I would recommend the answer given by David.
First, using asyncCall.Sync(); was incorrect. I am now using TEvent objects to wait for the actually event in question to occur. Threads can then continue with other work without make waiting threads wait longer than needed.
Second, I am now using a loop to catch exceptions that occur and sync the errors back to my main thread. For example, one thread may look like this:
procedure TUpdater.needsToGoSecond();
begin
fAsyncThreads.Add(TAsyncCalls.Invoke(procedure
begin
//initial stuff
myEvent.Wait();
//stuff that needs to go second
if (possiblyTrueCondition = True) then
raise Exception.Create('Error Message');
TAsyncCalls.VCLSync(procedure
begin
notifyUpdates();
end);
end));
end;
And I have another thread catching and raising the exceptions elsewhere:
procedure TUpdater.catchExceptions();
begin
fAsyncCatchExceptions := TAsyncCalls.Invoke(procedure
var
asyncThread: IAsyncCall;
errorText: string;
begin
while(true)do
begin
for asyncThread in fAsyncThreads do
begin
if(Assigned(asyncThread)) and (asyncThread.Finished)then
begin
try
asyncThread.Sync();
except on E: Exception do
begin
errorText := E.Message;
TAsyncCalls.VCLInvoke(procedure begin raise Exception.Create(errorText); end);
end;
end;
fAsyncThreads.Remove(asyncThread);
end;//if
end;//for
Sleep(2000);
end;//while
end);
end;
It appears that the exception must be thrown by a VCLInvoke (or TThread.Queue) call rather than a VCLSync (or TThread.Synchronize) call. When synchronizing, I think the outer AsyncCall catches the exception and prevents it from being shown by the GUI. Also, my catch loop does not as yet create a copy of the exception to raise in the main thread. Because you seem to need to queue the raise command, you cannot re-raise the current exception. You'll get an access violation as it will most like be cleaned up by the time the GUI gets to it.
Well, since we came to inexact answers, here is one more.
OmniThreadLibrary: http://otl.17slon.com/
Forum (and author is very responsive there): http://otl.17slon.com/forum/
Async example: http://www.thedelphigeek.com/2012/07/asyncawait-in-delphi.html
Book sample: http://samples.leanpub.com/omnithreadlibrary-sample.pdf
Section 2.1 introduces Async.
Section 2.1.1 exactly covers exceptions handling in Async
More on exceptions in OTL: http://www.thedelphigeek.com/2011/07/life-after-21-exceptions-in.html
it does not use Async namely, and threading primitives are kept uniform, so it should apply for Async as well