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.
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.
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.
Glad I could help. 🙂
Hi Mason, what’s difference if we use TQueue instead of TThreadedQueue (which doesn’t exist in D2010)? After all the code is all in the main thread. Actually I changed it to TQueue and it seems that everything is OK.
Thanks again for this 🙂
The difference is that TThreadedQueue can *receive* events from other threads safely, because it has locking built in. Everything comes out on the main thread, sure, but I wanted this to be essentially a replacement for TThread.Queue, so that requires a thread-safe queue. If you don’t have TThreadedQueue available, though, you can simulate it with a critical section.
@Mason, thanks for the clear answer!
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;
Exactly. One queue is enough. Two is too many.
Be careful with this. TProc is an anonmyous method, implemented internally as an interface. This means it’s a managed type, and by the time the message loop pulls it out of the Windows event queue, it could very well have already been freed. If there was any good way to do this without a local queue, I would have done it, but I don’t think you can avoid it with pointer tricks like this.
Nope. This technique is fine. PProc^ := Proc takes a reference.
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) 🙂
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.
Mason, thanks for enabling the comments, continued at the original post (for those few in the future who would follow this 🙂
“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.