So, I always faced the main headaches when streaming processing in delphi xe4-6, whether from threads that are not running, exception handling causes application crashes or just the on terminate method that will never be called. All the workarounds that I was offered have become very tedious in the issues that still haunt me in XE6. My code usually looked something like this:
procedure TmLoginForm.LoginClick(Sender: TObject); var l:TLoginThread; begin SyncTimer.Enabled:=true; l:=TLoginThread.Create(true); l.username:=UsernameEdit.Text; l.password:=PasswordEdit.Text; l.FreeOnTerminate:=true; l.Start; end; procedure TLoginThread.Execute; var Success : Boolean; Error : String; begin inherited; Success := True; if login(USERNAME,PASSWORD) then begin // do another network call maybe to get dif data. end else begin Success := False; Error := 'Login Failed. Check User/Pass combo.'; end; Synchronize( procedure if success = true then begin DifferentForm.Show; end else begin ShowMessage('Error: '+SLineBreak+Error); end; SyncTimer.Enabled := False; end); end;
And then I came across this device from samples in Delphi and from the forums:
unit AnonThread; interface uses System.Classes, System.SysUtils, System.Generics.Collections; type EAnonymousThreadException = class(Exception); TAnonymousThread<T> = class(TThread) private class var CRunningThreads:TList<TThread>; private FThreadFunc: TFunc<T>; FOnErrorProc: TProc<Exception>; FOnFinishedProc: TProc<T>; FResult: T; FStartSuspended: Boolean; private procedure ThreadTerminate(Sender: TObject); protected procedure Execute; override; public constructor Create(AThreadFunc: TFunc<T>; AOnFinishedProc: TProc<T>; AOnErrorProc: TProc<Exception>; ACreateSuspended: Boolean = False; AFreeOnTerminate: Boolean = True); class constructor Create; class destructor Destroy; end; implementation {$IFDEF MACOS} uses {$IFDEF IOS} iOSapi.Foundation {$ELSE} MacApi.Foundation {$ENDIF IOS} ; {$ENDIF MACOS} { TAnonymousThread } class constructor TAnonymousThread<T>.Create; begin inherited; CRunningThreads := TList<TThread>.Create; end; class destructor TAnonymousThread<T>.Destroy; begin CRunningThreads.Free; inherited; end; constructor TAnonymousThread<T>.Create(AThreadFunc: TFunc<T>; AOnFinishedProc: TProc<T>; AOnErrorProc: TProc<Exception>; ACreateSuspended: Boolean = False; AFreeOnTerminate: Boolean = True); begin FOnFinishedProc := AOnFinishedProc; FOnErrorProc := AOnErrorProc; FThreadFunc := AThreadFunc; OnTerminate := ThreadTerminate; FreeOnTerminate := AFreeOnTerminate; FStartSuspended := ACreateSuspended; //Store a reference to this thread instance so it will play nicely in an ARC //environment. Failure to do so can result in the TThread.Execute method //not executing. See http://qc.embarcadero.com/wc/qcmain.aspx?d=113580 CRunningThreads.Add(Self); inherited Create(ACreateSuspended); end; procedure TAnonymousThread<T>.Execute; {$IFDEF MACOS} var lPool: NSAutoreleasePool; {$ENDIF} begin {$IFDEF MACOS} //Need to create an autorelease pool, otherwise any autorelease objects //may leak. //See https://developer.apple.com/library/ios/
Why does this part of the above threads work flawlessly in 100% of cases, and sometimes my code crashes? For example, I can execute the same thread 6 times in a row, but then, possibly, at the seventh (or first in this case) time, it causes the application to crash. No exceptions ever occurred during debugging, so I donβt know where to start fixing the problem. Also, why do I need a separate timer that calls "CheckSynchronize" for my code so that GUI updates are executed, but this is not necessary when I use the anon thread block?
Perhaps someone can point me in the right direction to ask this question elsewhere if there is no place here. Sorry, I'm already diving into the documentation, trying to understand everything.
Here is an example thread that can run 20 times in a row, but then randomly cause the application to crash
inherited; try SQL:= 'Some SQL string'; if GetSQL(SQL,XMLData) then synchronize( procedure var i:Integer; begin try mTasksForm.TasksListView.BeginUpdate; if mTasksForm.TasksListView.Items.Count>0 then mTasksForm.TasksListView.Items.Clear; XMLDocument := TXMLDocument.Create(nil); XMLDocument.Active:=True; XMLDocument.Version:='1.0'; XMLDocument.LoadFromXML(XMLData); XMLNode:=XMLDocument.DocumentElement.ChildNodes['Record']; i:=0; if XMLNode.ChildNodes['ID'].Text <>'' then while XMLNode <> nil do begin LItem := mTasksForm.TasksListView.Items.AddItem; with LItem do begin Text := XMLNode.ChildNodes['LOCATION'].Text; Detail := XMLNode.ChildNodes['DESC'].Text + SLineBreak+ 'Assigned To: '+XMLNode.ChildNodes['NAME'].Text tag := StrToInt(XMLNode.ChildNodes['ID'].Text); color := TRectangle.Create(nil); with color do begin if XMLNode.ChildNodes['STATUS'].Text = STATUS_DONE then fill.Color := TAlphaColors.Lime else if XMLNode.ChildNodes['STATUS'].Text = STATUS_OK then fill.Color := TAlphaColors.Yellow else fill.Color := TAlphaColors.Crimson; stroke.Color := fill.Color; ButtonText := XMLNode.ChildNodes['STATUS'].Text; end; Bitmap := Color.MakeScreenshot; end; XMLNode:=XMLNode.NextSibling; end; finally mTasksForm.TasksListView.EndUpdate; for i := 0 to mTasksForm.TasksListView.Controls.Count-1 do begin if mTasksForm.TasksListView.Controls[I].ClassType = TSearchBox then begin SearchBox := TSearchBox(mTasksForm.TasksListView.Controls[I]); Break; end; end; SearchBox.Text:=' '; SearchBox.text := ''; //have in here because if the searchbox has text, when attempting to add items then app crashes end; end) else error := 'Please check internet connection.'; finally synchronize( procedure begin if error <> '' then ShowMessage('Erorr: '+error); mTasksForm.Spinner.Visible:=false; mTasksForm.SyncTimer.Enabled:=false; end); end; end;
here is the getSQL method
function GetSQL(SQL:String;var XMLData:String):Boolean; var PostResult, ReturnCode : String; PostData : TStringList; IdHTTP : TIdHTTP; XMLDocument : IXMLDocument; XMLNode : IXMLNode; Test : String; begin Result:=False; XMLData:=''; XMLDocument:=TXMLDocument.Create(nil); IdHTTP:=TIdHTTP.Create(nil); PostData:=TStringList.Create; PostData.Add('session='+SessionID); PostData.Add('database='+Encode(DATABASE,'')); PostData.Add('sql='+Encode(SQL,'')); IdHTTP.Request.ContentEncoding:='UTF-8'; IdHTTP.Request.ContentType:='application/x-www-form-urlencoded'; IdHTTP.ConnectTimeout:=100000; IdHTTP.ReadTimeout:=1000000; try PostResult:=IdHTTP.Post(SERVER_URL+GET_METHOD,PostData); XMLDocument.Active:=True; XMLDocument.Version:='1.0'; test := Decode(PostResult,''); XMLDocument.LoadFromXML(Decode(PostResult,'')); XMLNode:=XMLDocument.DocumentElement; try ReturnCode:=XMLNode.ChildNodes['status'].Text; except ReturnCode:='200'; end; if ReturnCode='' then begin ReturnCode:='200'; end; if ReturnCode='200' then begin Result:=True; XMLData:=Decode(PostResult,''); end; except on E: Exception do begin result:=false; end; end; PostData.Free; IdHTTP.Free; end;