Delayed action

When you work with UIs, order of operations can be very important.  Windows’s UI works based on an event queue and a message pump that reads events from the queue and dispatches them.  And since the UI is single-threaded, this means that at any time, there could be pending events about to execute while you’re handling some UI code.

Sometimes you’ll get into a situation where you need to execute something, but not right away; you need it to happen after all of the pending events have processed.  Now, obviously, the best way to make something happen after everything in the queue has processed is to put something else onto the queue directly behind everything that’s currently in there.  But the tricky part is what happens when it comes back out.

Messages get posted to a HWND (window handle) and get dispatched to the associated window’s WndProc routine, where they’re handled based on their message type, which is a 2-byte integer value.  There are a bunch of reserved constant values with specific meanings to Windows. WM_PAINT, for example, tells a visual control to repaint itself.  But there’s also a reserved value called WM_USER.  Everything from this constant onward can be used as a user-defined message.

Delphi makes associating a message constant with a method of an object very easy; all you need to do is put a message directive after the declaration, with a value indicating the message type it should respond to.  But because messages get dispatched to a HWND, this will only work for a pretty narrow subset of objects.

Interestingly enough, there is a way built in to Delphi to post a message to the queue of the main (UI) thread, which will execute an arbitrary procedure when the message is read.  It’s the TThread.Queue routine.  Unfortunately, as the name implies, it only works correctly when used from another thread.  The documentation warns you not to call TThread.Queue from the main thread.  Looking through the code, I’m not completely sure what will happen if you do, but it looks like it will just execute your routine immediately instead of posting it to the event queue for delayed execution.

So the obvious solution if you want to queue something up from the main thread is a bit ugly: spawn a thread that calls TThread.Queue and then terminates.  But now you’re introducing all sorts of new things that can go wrong!

Well, I’d run into a need to do this a handful of times, and I’d always had a form or other control available that I could attach a message handler to… until last week.  I was inside an object that is not a visual control, but which creates a form, calls ShowModal on it, and then releases the form when it’s done.  Problem is, both that modal form and the base form that the current code was working with (but was not part of) displayed OpenGL contexts, and due to various factors the GL drawing state was getting corrupted.  So I needed some way to reset things, but I had to wait until the modal form was finished cleaning up.  The Release method works based on delayed action, to let a form finish any queued processing before destroying it, but that means that my reset code had to also execute delayed.

After thinking about it for a while, here’s what I came up with.  In 90 lines of code, it duplicates the functionality of TThread.Queue–taking an anonymous procedure with no arguments and queuing it up for delayed execution on the main thread–except that it works correctly when called from the main thread.

[code lang="Delphi"]
{*****************************************************************************
* The contents of this file are used with permission, subject to
* the Mozilla Public License Version 1.1 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
* http://www.mozilla.org/MPL/MPL-1.1.html
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
*****************************************************************************
*
* This file was created by Mason Wheeler.  He can be reached for support at
* www.turbu-rpg.com.
*****************************************************************************}

unit delayedAction;

interface
uses
  SysUtils;

procedure DelayExec(const action: TProc);

implementation
uses
   Windows, Messages, Generics.Collections, Classes, Forms;

type
   TDelayQueue = class
   private
      FQueue: TThreadedQueue;
      FHandle: HWND;
      procedure MainWndProc(var Msg: TMessage);
      procedure Dequeue;
   public
     constructor Create;
     destructor Destroy; override;
   end;

{ TDelayQueue }

constructor TDelayQueue.Create;
begin
   FHandle := AllocateHWnd(self.MainWndProc);
   FQueue := TThreadedQueue.Create;
end;

destructor TDelayQueue.Destroy;
begin
   FQueue.Free;
   DeallocateHWnd(FHandle);
   inherited Destroy;
end;

procedure TDelayQueue.Dequeue;
var
   proc: TProc;
begin
   proc := FQueue.PopItem();
   proc();
end;

procedure TDelayQueue.MainWndProc(var Msg: TMessage);
begin
   try
      if Msg.Msg = WM_USER then
         dequeue
      else DefWindowProc(FHandle, msg.Msg, msg.WParam, msg.LParam);
   except
      Application.HandleException(Self);
   end;
end;

var
   delay: TDelayQueue;

procedure DelayExec(const action: TProc);
begin
   delay.FQueue.PushItem(action);
   PostMessage(delay.FHandle, WM_USER, 0, 0);
end;

initialization
   delay := TDelayQueue.Create;
finalization
   delay.Free;
end.
[/code]

I figured I may as well share it. Feel free to use this if you’re ever in a situation where you need delayed execution.

14 Comments

  1. C Johnson says:

    Worth noting is the TApplication.OnIdle. That only executes once the app’s message queue goes empty. Just make sure you set done to true when done or you’ll seriously peg the cpu with an idle loop.

  2. Edwin says:

    Great! I had the exact need and had a similar idea during the development of LIVEditor, but I haven’t come up a neat implementation like yours, thanks!

    @C Johnson, TApplication.OnIdle is good, but doesn’t allow a clear organization of the code in this situation.

  3. Leif Uneus says:

    I think you can remove the TThreadedQueue by doing this instead:
    (Tested on XE3).

    Procedure DelayExec( const aProc: TProc);
    { Posts the anonymous TProc() to the message queue so it can call aProc()
    in the main thread. }
    {- Example:
    QueueAnonProcToMainThread(
    procedure
    begin
    Memo1.Lines.Add(‘Hello’);
    end);
    }
    var
    Proc: TProc absolute aProc;
    PProc: ^TProc;
    begin
    New(PProc);
    PProc^ := Proc;
    PostMessage( delay.FHandle,
    WM_USER,
    WPARAM(PProc),0);
    end;

    procedure TDelayQueue.MainWndProc(var Msg: TMessage);

    Procedure AnonProcPerform;
    {- Extracts the anonymous TProc and executes it. }
    var
    PProc : ^TProc;
    Proc : TProc;
    begin
    PProc := Pointer(Msg.wParam);
    Proc := PProc^;
    Try
    Proc();
    Finally
    Dispose(PProc);
    End;
    end;

    begin
    try
    if Msg.Msg = WM_USER then
    AnonProcPerform
    else
    DefWindowProc(FHandle, msg.Msg, msg.WParam, msg.LParam);
    except
    Application.HandleException(Self);
    end;
    end;

  4. Max Vlasov says:

    Sorry for posting not exactly at the place it belongs, but that post was closed (http://tech.turbu-rpg.com/52/how-to-leak-a-class-you-never-defined) and this is the closest open about anonymous methods.

    The solution to the memory leak http://stackoverflow.com/questions/6273376/memory-leaks-happens-in-nested-anonymous-method#18002857 is looks promising from my pov. I added a comment about this at QC83259.

    The qc is still open, seems like everyone inside emb thinks this problem has no solution. Please, consider testing your cases and reopening comments to the post (it was linked in the stackoverflow post of the qc reporter) 🙂

    • Mason Wheeler says:

      Not sure how that got closed, but it’s reopened now.

      That fix looks like it will work as long as you don’t pass either of the anonymous methods generated in the procedure outside if it to be used at some point after the procedure returns. Otherwise you’ll end up with access violations. But it’s a good workaround.

  5. Remy Lebeau says:

    “It’s the TThread.Queue routine. Unfortunately, as the name implies, it only works correctly when used from another thread.”

    https://quality.embarcadero.com/browse/RSP-15427

    “The documentation warns you not to call TThread.Queue from the main thread. Looking through the code, I’m not completely sure what will happen if you do, but it looks like it will just execute your routine immediately instead of posting it to the event queue for delayed execution.”

    Yes, that is exactly what happens.

Leave a Reply