While developing Windows application we often use event handlers and timers. Each of these things are designed to perform some different tasks. For example event handlers usually are defined to perform certain actions when the source component state changes or some action happens. Timers are usually used to perform some actions multiple times with a certain interval. But sometimes timers are used to run a task just once. There can be a different reasons of that:
- You want to execute some code once you exit from the event handler (maybe FormCreate event or similar);
- Or you want to execute certain code when all the windows messages are processed (say controls are repainted etc.);
- You are in transaction processing and want to show the message to the user after it’s finished.
In such cases we cannot use threaded execution as the execution time of thread is unpredictable (we cannot guarantee the order of the thread execution – at least in a simple way just to solve such a small task of running the few lines of code). But what is inconvenient that we need to create the timer, then assign the event and then set the Interval to something very small like 1 or 5 ms. That always feels like a workaround or something which is not a nice solution.
Ok, but there is another way – we can define the message handler on a form for example which handles WM_USER + X and then post a message to handle to execute our code delayed and everything is cool! Right, this solution looks much better and more professional. But there is another but! Once you need to use some context values from the place where you initiate the code execution (post the message) you will need to move them to private section of the class to pass them to the message handler (unless you cannot do that just with LParam and RParam of the message itself).
For such cases we can use module called uProcQueue. The module contains the class which allows you to enqueue the anonymous procedure (so that you can capture all the context values) and it performs it execution in order of the windows messages. So the procedures are executed in the order as they were enqueued.
So the use of this class is following:
ProcQueue.Enqueue(
procedure
begin
// There you write your code to do
end, Owner);
Optionally you can specify the second parameter – an owner of the procedure queued. This is useful to make sure that your procedure is not executed when your components is already destroyed (this can happen, as the procedure is not executed right after the line where it’s enqueued, but after all the Windows messages before that are processed by the application). So when Owner is passed then the ProcQueue class create the special destroy monitor (actually just another component which is created with the owner provided) which removes the enqueued procedure from the queue when the owner is destroyed.
Example 1
So to illustrate a use I will provide a simple example. The example is not a good way to do things in real life – so that’s just an illustration. So image we want to show modal message box on FormCreate. If we do that just by adding the line to the FormCreate then we get the message box before the form is shown itself. But if we write the following code:
procedure TForm1.FormCreate(Sender: TObject); begin inherited; ... ProcQueue.Enqueue( procedure begin ShowMessage('Hello after show!') end); end;
Then the message box will be shown right after you see your form on a screen.
Example 2
What if you want to free the control/component from event handler (again I remind you that this is actually not a good way of doing things and better to avoid such cases, but this works good as an example).
procedure TForm1.Button1Click(Sender: TObject); begin ProcQueue.Enqueue( procedure begin Button1.Free; end, Button1); end;
For sure this code will also work without the ProcQueue, but that does not work with other components like TDataSet or any others who have some code referencing the instance of the component after calling the event handler. Let’s look at TDataSet.Delete procedure:
procedure TDataSet.Delete;
begin
...
CheckOperation(InternalDelete, FOnDeleteError);
SetState(dsBrowse);
...
DoAfterDelete; // This calls the AfterDelete event handler
DoAfterScroll;
...
end;
So if you call Free in AfterDelete event handler you can get an access violation. Because after processing the AfterDelete event the DoAfterScroll method is executed which accesses the member of the instance which is already released.
In this case if you use ProcQueue.Enqueu there will be no issues as the code will be executed after whole chain of the code is executed and message processing in application occurs:
procedure TForm1.ADOTable1AfterDelete(DataSet: TDataSet); begin ProcQueue.Enqueue( procedure begin ADOTable1.Free; end, ADOTable1); end;
The code and examples there are provided without any warranty. If you plan to use it – then do it at your own risk. I take no responsibility for any damage or issues caused by the code provided there.
The class supports also non-Windows platforms and uses the threading library but loses the ownership option (an automatic releasing when owner is released):
TTask.Run(
procedure
begin
TThread.Queue(nil,
procedure
begin
// a code to be queued
end)
end);
Full source code is available on GitHub.
If you want a target for a windows message, don’t derive from TCustomForm. Use AllocateHWnd.
LikeLike
That’s what I used at work. But I did that on weekend and did not remember that 🙂 So I just stick to that for simplicity. But I will adjust that and use that approach as well.
As well at work I used a TQueue with events and TList with owners. And then in WndProc after Dequeue I checked for index of owner. But this time I think this solution is more simple.
Thank you for your feedback!
LikeLiked by 1 person
Fixed on GitHub.
LikeLiked by 1 person
Very useful! Thanks, will integrate it to a new software of mine 🙂
LikeLike
You are welcome!
LikeLike