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;

Comments

  1. You really should avoid doing anything with the UI directly from a background thread.

    If 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;

    ReplyDelete
  2. 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.

    ReplyDelete
  3. Lars Fosdal I do nothing with UI from thread, just display messages in case they are.

    ReplyDelete
  4. R Gosp Your error message says you are trying to display it from the thread context, and not the UI context.

    ReplyDelete
  5. Lars Fosdal That is where I need help, how to display the messages from threads in main UI?

    ReplyDelete
  6. R 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.

    Perhaps someone experienced in Android could suggest the best signalling method? Under Windows, I'd send a message.

    ReplyDelete
  7. R Gosp Use TThread.Queue or TThread.Synchronize to execute the message dialog in the context of the main thread.

    This 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;

    ReplyDelete
  8. I have nothing positive to say about Synchronize, though. It has failed me in mysterious ways every time I've tried to use it.

    ReplyDelete
  9. What happens if lots of threads start throwing messages up at the same time?

    ReplyDelete
  10. David 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.

    A better solution is left as an exercise for the reader.

    ReplyDelete
  11. 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.

    ReplyDelete
  12. Primož Gabrijelčič that procedure is executed inside thread?

    ReplyDelete
  13. R Gosp "that" = anonymous procedure? It is executed in the main thread.

    ReplyDelete
  14. Primož 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?

    ReplyDelete
  15. I 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.

    ReplyDelete
  16. The solution for my question:

    procedure 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

    ReplyDelete
  17. 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.

    ReplyDelete
  18. Can I ask, one more time, why do you want your threads to show message dialogs?

    ReplyDelete
  19. David Heffernan to raise error messages if are, the functions are test if services are running, if fiscal printer is connected ...

    ReplyDelete
  20. Primož Gabrijelčič the sample I found somewhere on net, will check without task

    ReplyDelete
  21. R 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.

    ReplyDelete
  22. David 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

    ReplyDelete
  23. The 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.

    ReplyDelete
  24. A 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

Post a Comment