Jump to content
Nasreddine

Async await with blocking mode using Application.ProcessMessage(var Msg: TMsg)

Recommended Posts

I have the following style of using Async Await in delphi
 

Async(
    procedure(aArgs: TnArgs)
    begin
      // long time work on worker thread using TThread
    end).
  SetReportInThreadException(False).
  Await(
    procedure(aArgs: TnArgs)
    begin
      // result executed on main thread using TThread.Synchronize();
    end
  );

this works wonderfully for fire and forget pattern, but that means any code after this Async/Await combo will be executed without waiting for the Await part,

What I want to do now is refactor my Async/Await code and use 
 

function TApplication.ProcessMessage(var Msg: TMsg): Boolean;

to block the current execution until I receive a message indicating that the Await part was executed and it is safe to continue the execution of the rest of my code on the main again 

example of A

procedure TAwaiter.AwaitBlocked(aProcArg: TProc<TnArgs>; out AAwaiter: IAwaiter);
begin

  FnThread := TnThread.SetupAsync(FAsyncArg, FProgress, aProcArg, FReportInThreadException);
  FnThread.Start;
  AAwaiter := Self;
  
  while not TnApplication(Application).nProcessMessage(FTaskID) in [WM_QUIT, WM_MYThreadTaskEndedMsg] do {loop};

end;

AwaitBlocked method will create the thread and start it and then that thread will post a message when done.

Now to the question, based on your experience, Do you see anything wrong with the design?

Keep in mind the following is all taken into consideration:
   - The user clicks again on the button.
   - any other special handling that will free the resources needed for the blocked instruction after the await.
   - Exceptions handling. 

the end result code 

Async(
    procedure(aArgs: TnArgs)
    begin
      // long time work on worker thread using TThread
    end).
  SetReportInThreadException(False).
  AwaitBlocked(
    procedure(aArgs: TnArgs)
    begin
      // result executed on main thread using TThread.Synchronize();
    end
  );

// other code to execute that is dependent on what happens in the code executed in the await part, or needs to wait for that part to be done


 

Edited by Nasreddine

Share this post


Link to post

This scenario doesn't sound like it needs Async at all.

  • Start a thread with an OnTerminate Event
  • Disable all user input and show a busy signal
  • When the thread completes the event fires and all reverts back to normal

For the few cases that Async is needed there is `MsgWaitForMultipleObjects`..

 

 

Share this post


Link to post

@FredS What you said is true and I already do it, what I want is to block the current execution until the thread finishes but keep the app responsive. MsgWaitForMultipleObjects will freeze the app waiting for the thread to finish. 

Edited by Nasreddine

Share this post


Link to post
14 minutes ago, Nasreddine said:

MsgWaitForMultipleObjects will freeze the app waiting for the thread to finish. 

Not if you pump the message queue whenever MsgWaitForMultipleObjects() tells you that messages are waiting.

Share this post


Link to post

@Remy Lebeau In that case wouldn't that be the same as my Idea above? 

block current execution while pumping the message queue,
and thank you by the way I did not know you can set it up that way, I always thought it was meant to wait for events. 

Share this post


Link to post
5 hours ago, Nasreddine said:

will freeze the app waiting

Not if you you code it to respond to the Classes.SyncEvent : https://stackoverflow.com/a/61022449

 

2 hours ago, Nasreddine said:

block current execution while pumping the message queue,

I use both, in one case I have many threads collecting data and triggering updates to a form while getting ready to launch another Task I don't want to pause the updates so a call gets wrapped in an Async which uses the trick above.. but I try to only do that for single calls that are guaranteed to take a short time.

Edited by FredS

Share this post


Link to post

@FredS  @Remy Lebeau Thank you very much guys, I'm refactoring my code now to use MsgWaitForMultipleObjects and see the difference
 

Remy Do you have an example how to pump the message queue in this case? I would like to compare when I'm done.

 

Share this post


Link to post
3 hours ago, Nasreddine said:

@FredS  @Remy Lebeau Thank you very much guys, I'm refactoring my code now to use MsgWaitForMultipleObjects and see the difference
 

Remy Do you have an example how to pump the message queue in this case? I would like to compare when I'm done.

 

Here is an example from my old threading library code. FSignal is a TSimpleEvent the thread will signal when it has finished its work.

 

{! Wait for the call to complete. Optionally we can process a subset
  of messages during the wait. That complicates the timeout handling,
  though, since the wait can be interrupted by messages.}
function TAsyncCall.WaitForCompletion(TimeoutMSecs: Cardinal;
  ProcessMessages: Boolean): TWaitResult;
var
  TargetTime, CurrentTime: int64;
  H: THandle;
  Ret: DWORD;

  function TimeRemaining: DWORD;
  begin
    if TimeoutMSecs = INFINITE then
      Result := INFINITE
    else begin
      CurrentTime := ToInt64(GetTickCount());
      if CurrentTime > TargetTime then
        Result := 0
      else begin
        Result := TargetTime - CurrentTime;
        if Result > TimeoutMSecs then
          Result := 0;  // GetTickCount rolled over
      end;
    end;
  end;

  {! We want to block most user input here but allow paint messages
    to be processed and also non-client messages that relate to moving
    or minimizing the window to be acted upon. Timer messages are also
    processed! }
  procedure ProcessPendingMessages;
  var
    Msg: TMsg;
  const
    NSCMask = $FFF0;
  begin
    while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
      case Msg.message of
        WM_KEYFIRST..WM_KEYLAST, WM_MOUSEFIRST..WM_MOUSELAST: ; // swallow
        WM_NCMOUSEMOVE..WM_NCLBUTTONDBLCLK:
          case Msg.wParam of
            HTCAPTION, HTMINBUTTON: DispatchMessage(Msg);
          end; {case}
        WM_SYSCOMMAND:
          case Msg.wParam and NSCMask of
            SC_MINIMIZE, SC_RESTORE: DispatchMessage(Msg);
          end; {case}
      else
        DispatchMessage(Msg);
      end; {case}
  end;

begin
  EnsureSignalIsCreated;
  if FCompleted then
    Result := wrSignaled
  else
    if ProcessMessages then begin
      TargetTime := ToInt64(GetTickCount()) + TimeoutMSecs;
      H:= FSignal.Handle;
      Result := wrAbandoned;
      repeat
        Ret := MsgWaitForMultipleObjectsEx(
          1, H, TimeRemaining, QS_ALLINPUT, 0);
        case Ret of
          WAIT_ABANDONED : Exit;
          WAIT_FAILED    : Result := wrError;
          WAIT_TIMEOUT   : Result := wrTimeout;
          WAIT_OBJECT_0  : Result := wrSignaled;
          WAIT_OBJECT_0+1: ProcessPendingMessages;
        end; {case }
      until Result <> wrAbandoned;
    end {if}
    else
      Result := FSignal.WaitFor(TimeoutMSecs);
end;

 

Share this post


Link to post

 

20 hours ago, Nasreddine said:

I did not know you can set it up that way, I always thought it was meant to wait for events. 

That is what the "Msg" in its name stands for - it can wait on the Message Queue AND on a list of signallable objects.

17 hours ago, FredS said:

Not if you you code it to respond to the Classes.SyncEvent : https://stackoverflow.com/a/61022449

SyncEvent is used to let the main thread know when TThread.Synchronize()/TThread.Queue() requests are pending.  It is not used to let the main thread know when messages are pending.

5 hours ago, Nasreddine said:

Do you have an example how to pump the message queue in this case?

Simply call MsgWaitForMultipleObjects() with any of the flags that wait on the message queue (I usually just use QS_ALLINPUT), and then call Application.ProcessMessages() whenever MsgWaitForMultipleObjects() reports that messages are pending (when its return value is WAIT_OBJECT_0 + nCount).

Share this post


Link to post

@Remy Lebeau thank you very much for the advice, I will definitely write a better implementation now then the one I had before.

@PeterBelow thank you very much for sharing the part where you process the pending messages, it will help me a lot to make the finale idea come true.  

Again guys thank you very much for the help.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×