I need to make some background working methods for my application and do it with default Delphi PPL (in Firemonkey).
I need to make some background working methods for my application and do it with default Delphi PPL (in Firemonkey).
Everything is fine except the MessageDlg, it doesn't display and in debug I se a warning that it should be run in main ui thread.
The question is how to display (error) Messages from a method that is called in a thread?
Here is the example code:
procedure frmMain.TestFP;
begin
TTask.Run(
procedure
begin
GetFpData;
end
);
end;
procedure GetFpData;
begin
try
// some code to implement
except
on E: Exception do
TDialogService.MessageDialog(E.Message,TMsgDlgType.mtError,[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK, 0, nil);
end;
end;
Everything is fine except the MessageDlg, it doesn't display and in debug I se a warning that it should be run in main ui thread.
The question is how to display (error) Messages from a method that is called in a thread?
Here is the example code:
procedure frmMain.TestFP;
begin
TTask.Run(
procedure
begin
GetFpData;
end
);
end;
procedure GetFpData;
begin
try
// some code to implement
except
on E: Exception do
TDialogService.MessageDialog(E.Message,TMsgDlgType.mtError,[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK, 0, nil);
end;
end;
You really should avoid doing anything with the UI directly from a background thread.
ReplyDeleteIf only OmniThreadLibrary had been cross platform!
With VCL, I log the message to a queue, then poll the queue from the UI thread, using OmniThreadLibrary. You should do something similar - although you would have to implement your own thread safe queue.
uses
OtlCommon, OtlContainers;
In the form create
TCPLog := TOmniQueue.Create;
This method is assigned to a procedure variable in the background thread(s).
procedure TForm2.QueueFromThreadLog(const aMsg: String);
var
v: TOmniValue;
begin
v.AsString := aMsg;
TCPLog.Enqueue(v);
end;
In the UI Thread, this method is called periodically from a timer event, to output the log lines into a TMemo:
function TForm2.RefreshLog: Boolean;
var
v: TOmniValue;
begin
Result := not TCPLog.IsEmpty;
if Result
then begin
LogMemo.Lines.BeginUpdate;
try
while TCPLog.TryDequeue(v)
do LogMemo.Lines.Add(v.AsString);
finally
LogMemo.Lines.EndUpdate;
end;
end;
end;
You can explore the next way. Send to the method that initiates the thread, as a parameter, a "method pointer" (delegate) that is responsible for showing the MessageDlg, without stopping the current thread.
ReplyDeleteLars Fosdal I do nothing with UI from thread, just display messages in case they are.
ReplyDeleteR Gosp Your error message says you are trying to display it from the thread context, and not the UI context.
ReplyDeleteLars Fosdal That is where I need help, how to display the messages from threads in main UI?
ReplyDeleteR Gosp And that is what my example outlines. You pass the info you want to display to the UI thread through a thread safe mechanism. You then either have the UI thread continuously poll that mechanism when idle, or you signal the UI thread when there is something to retrieve.
ReplyDeletePerhaps someone experienced in Android could suggest the best signalling method? Under Windows, I'd send a message.
Lars Fosdal Something is going on. Slowly ... but it is moving ... https://github.com/gabr42/OmniThreadLibrary/tree/v4-develop-2
ReplyDeleteR Gosp Use TThread.Queue or TThread.Synchronize to execute the message dialog in the context of the main thread.
ReplyDeleteThis works fine on Windows:
procedure GetFpData;
var
eobj: TObject;
begin
try
raise Exception.Create('Test exception');
except
on E: Exception do begin
eobj := AcquireExceptionObject;
TThread.Queue(nil,
procedure
begin
TDialogService.MessageDialog(Exception(eobj).Message,
TMsgDlgType.mtError,
[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK, 0, nil);
ReleaseExceptionObject;
end);
end;
end;
end;
I have nothing positive to say about Synchronize, though. It has failed me in mysterious ways every time I've tried to use it.
ReplyDeleteWhat happens if lots of threads start throwing messages up at the same time?
ReplyDeleteDavid Heffernan Are you asking me? Yes, in that case the proposed solution wouldn't work as Delphi's design of acquire/release exception is just too stupid to believe.
ReplyDeleteA better solution is left as an exercise for the reader.
Primož Gabrijelčič I wasn't asking you. It is a thought experiment for the OP. Hopefully leading towards the realisation that only the UI thread should show UI.
ReplyDeletePrimož Gabrijelčič that procedure is executed inside thread?
ReplyDeleteR Gosp "that" = anonymous procedure? It is executed in the main thread.
ReplyDeletePrimož Gabrijelčič my question is not for anonimous procedure executed in TThread.Queue but the whole procedure GetFPData, can I execute it in TTask.Run(procedure begin GetFpData; end) and still display the message is in TThread.Queue's anonimous procedure?
ReplyDeleteI don't understand what you are asking. Why don't you merge my code into yours, run the program with the debugger and check for yourself which thread is executing what.
ReplyDeleteThe solution for my question:
ReplyDeleteprocedure frmMain.TestFP;
begin
TTask.Run(
procedure
begin
GetFpData;
end
);
end;
procedure GetFpData;
begin
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
try
// some code to implement
except
on E: Exception do
TDialogService.MessageDialog(E.Message,TMsgDlgType.mtError,[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK, 0, nil);
end;
end);
end;
and no warnings with main thread ui
Now you are executing ALL of your task (even '// some code to implement') in the main thread. You don't need tasks to do that.
ReplyDeleteCan I ask, one more time, why do you want your threads to show message dialogs?
ReplyDeleteDavid Heffernan to raise error messages if are, the functions are test if services are running, if fiscal printer is connected ...
ReplyDeletePrimož Gabrijelčič the sample I found somewhere on net, will check without task
ReplyDeleteR Gosp I get that you want to report back to the user that something failed. But it's surely wrong to attempt to do that from the thread or task.
ReplyDeleteDavid Heffernan why not? the application is working normaly and if any of services are down (all messages are loged in Events Manager) or if the fiscal printer is lost in connection - user is promted for that and can take the required action
ReplyDeleteThe user works in serial mode rather than parallel. The user can't process multiple messages at once. It's normal to queue these up and let the UI thread display them at a suitable moment.
ReplyDeleteA refactoing is needed for that along with other functionality, it is in todo, my option is for observer pattern, but for now as beta version it is a solution
ReplyDelete