Nasreddine 2 Posted February 10, 2022 (edited) 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 February 10, 2022 by Nasreddine Share this post Link to post
FredS 138 Posted February 10, 2022 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
Nasreddine 2 Posted February 10, 2022 (edited) @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 February 10, 2022 by Nasreddine Share this post Link to post
Remy Lebeau 1394 Posted February 10, 2022 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
Nasreddine 2 Posted February 10, 2022 @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
FredS 138 Posted February 11, 2022 (edited) 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 February 11, 2022 by FredS Share this post Link to post
Nasreddine 2 Posted February 11, 2022 @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
PeterBelow 238 Posted February 11, 2022 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
Remy Lebeau 1394 Posted February 11, 2022 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
Nasreddine 2 Posted February 11, 2022 @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