TAnonymousThread.Execute_Sync question..
D12.2, 32 bit App. I have implemented this code:
TAnonymousThread.Execute_Sync(procedure
begin
try
//
var dtStart: TDateTime := Now;
//
DBSBackup.Execute('BACKUP DATABASE "DBiArchive" AS "DBiArchive Manual Backup - ' + FormatDateTime('yyyy-mm-dd @ HHnn',
now) + '" TO STORE "Backups" COMPRESSION 9 INCLUDE CATALOG');
//
BackupsLog.Open;
BackupsLog.AppendRecord([dtStart, now, 'DBiArchive', 'Manual', AUD.UserID, now]);
BackupsLog.Close;
//
except
// Handle any exceptions that might occur during the execution of the SQL code
on E: Exception do ShowMessage('Error performing database backup: ' + E.Message);
//
end;
end
)
.Start;
I interpreted Execute_Sync would allow me to move the Form while the thread executed. It would seem not. How to be able to please?
Regards & TIA,Ian
Use Execute_Sync only if inside code updates UI like a button, label, etc. In your case seems you're not updating any UI interface.
Ahhh. Ok. Tks. So, using just "TAnonymousThread.Execute(procedure..." then, how do I keep the UI responsive so the form can be moved? Or do I use one of the other Quick.Threads options?
Sorry, Showmessage is UI dialog, you need Execute_Sync() Where you're calling this? There're any code after this call?
This is the complete function:
function TMainForm.ARBackup: Boolean;
begin
//
try
//
nlhWaiter2.Active := True;
//
// Create an anonymous thread to execute the SQL code
TAnonymousThread.Execute_Sync(procedure
begin
try
//
var dtStart: TDateTime := Now;
//
DBSBackup.Execute('BACKUP DATABASE "DBiArchive" AS "DBiArchive Manual Backup - ' + FormatDateTime('yyyy-mm-dd @ HHnn',
now) + '" TO STORE "Backups" COMPRESSION 9 INCLUDE CATALOG');
//
BackupsLog.Open;
BackupsLog.AppendRecord([dtStart, now, 'DBiArchive', 'Manual', AUD.UserID, now]);
BackupsLog.Close;
//
except
// Handle any exceptions that might occur during the execution of the SQL code
on E: Exception do ShowMessage('Error performing database backup: ' + E.Message);
//
end;
end
)
.Start;
//
Result := True;
//
nlhWaiter2.Active := False;
//
except
on E: Exception do
begin
//
Screen.Cursor := crDefault;
if (E is EDatabaseError) and (E is EEDBError) then
StyledTaskMessageDlg('Unhandled Database error!', 'An Unexpected database error # ' + EEDBError(E).ErrorCode.ToString + ' occurred!' +
sCRLF + EEDBError(E).ErrorMsg + sCRLF + 'Please inform your Workflow System Administrator.', mtError, [mbOK], 0)
else
StyledTaskMessageDlg('Unhandled error!', 'An unexpected error has occurred while doing the Backup!' + sCRLF + E.Message + sCRLF +
'Please inform your Workflow System Administrator.', mtError, [mbOK], 0);
//
btnClose.Enabled := True;
btnRestore.Enabled := True;
btnBackup.Enabled := True;
//
Backup.Refresh;
Backup.Last;
//
Screen.Cursor := crDefault;
//
lBackupDone := False;
//
Result := False;
//
end;
//
end;
//
end;
Try something like this:
function TMainForm.ARBackup: Boolean;
begin
// Create an anonymous thread to execute the SQL code
TAnonymousThread.Execute_Sync(procedure
begin
nlhWaiter2.Active := True;
//
var dtStart: TDateTime := Now;
//
DBSBackup.Execute('BACKUP DATABASE "DBiArchive" AS "DBiArchive Manual Backup - ' + FormatDateTime('yyyy-mm-dd @ HHnn',
now) + '" TO STORE "Backups" COMPRESSION 9 INCLUDE CATALOG');
//
BackupsLog.Open;
BackupsLog.AppendRecord([dtStart, now, 'DBiArchive', 'Manual', AUD.UserID, now]);
BackupsLog.Close;
Result := True;
end;
)
.OnException(procedure(aException : Exception)
begin
//
ShowMessage('Error performing database backup: ' + aException.Message);
Screen.Cursor := crDefault;
if (E is EDatabaseError) and (E is EEDBError) then
StyledTaskMessageDlg('Unhandled Database error!', 'An Unexpected database error # ' + EEDBError(E).ErrorCode.ToString + ' occurred!' +
sCRLF + EEDBError(E).ErrorMsg + sCRLF + 'Please inform your Workflow System Administrator.', mtError, [mbOK], 0)
else
StyledTaskMessageDlg('Unhandled error!', 'An unexpected error has occurred while doing the Backup!' + sCRLF + E.Message + sCRLF +
'Please inform your Workflow System Administrator.', mtError, [mbOK], 0);
//
btnClose.Enabled := True;
btnRestore.Enabled := True;
btnBackup.Enabled := True;
//
Backup.Refresh;
Backup.Last;
//
Screen.Cursor := crDefault;
//
lBackupDone := False;
//
Result := False;
//
end)
.OnTerminate_Sync(procedure
begin
nlhWaiter2.Active := False;
end;)
.Start;
end;
Thank you.
Unfortunately Result does not carry into the Thread. Nor it seems does E for the Exception.
Sorry, I did without an Delphi IDE. You need to change E variable with aException procedure parameter inside OnException. Result is not needed because you have to change to procedure. No senses to have a function than launches an async method without waiting for them and get a result. After .start, delphi continues with execution, but TAnonymousThread opens a separate execution thread.
@ijbranch Hi, this is how you can use TAnonymousThread in your case:
function TMainForm.ARBackup: Boolean;
begin
nlhWaiter2.Active := True;
TAnonymousThread.Execute(procedure
begin
//
var dtStart: TDateTime := Now;
//
DBSBackup.Execute('BACKUP DATABASE "DBiArchive" AS "DBiArchive Manual Backup - ' + FormatDateTime('yyyy-mm-dd @ HHnn',
now) + '" TO STORE "Backups" COMPRESSION 9 INCLUDE CATALOG');
//
BackupsLog.Open;
BackupsLog.AppendRecord([dtStart, now, 'DBiArchive', 'Manual', AUD.UserID, now]);
BackupsLog.Close;
end)
.OnTerminate_Sync(procedure
begin
//
Result := True;
//
nlhWaiter2.Active := False;
//
end)
.OnException(procedure (E: Exception)
begin
Screen.Cursor := crDefault;
if (E is EDatabaseError) and (E is EEDBError) then
StyledTaskMessageDlg('Unhandled Database error!', 'An Unexpected database error # ' + EEDBError(E).ErrorCode.ToString + ' occurred!' +
sCRLF + EEDBError(E).ErrorMsg + sCRLF + 'Please inform your Workflow System Administrator.', mtError, [mbOK], 0)
else
StyledTaskMessageDlg('Unhandled error!', 'An unexpected error has occurred while doing the Backup!' + sCRLF + E.Message + sCRLF +
'Please inform your Workflow System Administrator.', mtError, [mbOK], 0);
//
btnClose.Enabled := True;
btnRestore.Enabled := True;
btnBackup.Enabled := True;
//
Backup.Refresh;
Backup.Last;
//
Screen.Cursor := crDefault;
//
lBackupDone := False;
//
Result := False;
end
)
.Start;
end;
A few things to consider in your code:
- nlhWaiter2 should be disabled when an exception occurs as well
- Should the buttons be enabled when the thread terminates without exceptions?
- You've got Screen.cursor twice
- I would probably have the "Result:=false" at the beginning and then alter the value only in successful completion
Hope this helps
jkour - Thank you. Appreciated.
I now have this:
function TBackUpsForm.ARBackup: Boolean; begin // nlhWaiter2.Active := True; Result := False; // TAnonymousThread.Execute(procedure begin // var dtStart: TDateTime := Now; // dmB.DBSBackup.Execute('BACKUP DATABASE "DBiArchive" AS "DBiArchive Manual Backup - ' + FormatDateTime('yyyy-mm-dd @ HHnn', now) + '" TO STORE "Backups" COMPRESSION 9 INCLUDE CATALOG'); // BackupsLog.Open; BackupsLog.AppendRecord([dtStart, now, 'DBiArchive', 'Manual', AUD.UserID, now]); BackupsLog.Close; end) .OnTerminate_Sync(procedure begin // Result := True; // nlhWaiter2.Active := False; // end) .OnException(procedure(E: Exception) begin Screen.Cursor := crDefault; // nlhWaiter2.Active := False; // if (E is EDatabaseError) and (E is EEDBError) then StyledTaskMessageDlg('Unhandled Database error!', 'An Unexpected database error # ' + EEDBError(E).ErrorCode.ToString + ' occurred!' + sCRLF + EEDBError(E).ErrorMsg + sCRLF + 'Please inform your Workflow System Administrator.', mtError, [mbOK], 0) else StyledTaskMessageDlg('Unhandled error!', 'An unexpected error has occurred while doing the Backup!' + sCRLF + E.Message + sCRLF + 'Please inform your Workflow System Administrator.', mtError, [mbOK], 0); // Backup.Refresh; Backup.Last; // lBackupDone := False; // end ) .Start; // btnClose.Enabled := True; btnRestore.Enabled := True; btnBackup.Enabled := True; // end;
Delphi is telling me that Result in .OnTerminate
Regards, Ian
Hi @ijbranch ,
Yes, that is correct. Result to a function can't be captured within a thread.
So, there are two options:
- Either you have a Status field in TMainForm that is altered
- Or, you use a different technique. This is how I generally do this (keep in mind that I am writing off the top of my head, as I am away from my PC right now).
function TMainForm.ARBackup: boolean;
var
shouldContinue: boolean;
intResult: boolean;
begin
result:=false;
// nlhWaiter2.Active:=true;
shouldContinue:=false;
intResult:=false;
TAnonymousThread.Execute(procedure
begin
//
// ---> do stuff here
shouldContinue:=true;
intResult:=true; // Or, false, depending on the result in the database
end)
.OnTerminate_Sync(procedure
begin
//
shouldContinue:=true;
intResult := True; // Or, false, depending on the result in the database
//
nlhWaiter2.Active := False;
//
end)
.OnException(procedure (E: Exception)
begin
// ---> do stuff here
shouldContinue:=true;
intResult:=false;
end
)
.Start;
while not shouldContinue do
; /// ---> or, Sleep(1);
result:=intResult;
end;
I mentioned earlier that you can have a Status field in the class. If you do this, you can have the Styled Message dialogs outside the thread and after the while...do loop.
This will also allow you to move all the database operations to a class separating the business model from the UI part of your application.