Procedure queue implementation with messages

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.

5 comments

    1. 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!

      Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s