Categories
Source Code

Run Outside the Handler or Making a Non-Blocking Call

Have you ever had some code you wanted to run outside of the event handler that causes the code to run? If not, then this blog post isn’t for you. I’m not here to debate why you would want to do that, or if it is a good idea or not. I just know there’ve been times I’ve needed my code to run outside the event handler, or just a bit later.

One use case example: You are calling a slow routine (Network I/O maybe) and don’t want to freeze the UI while you wait for it to execute.

Still with me? Good. What I used to do was drop a TTimer on the form and set the Interval to 1, then enable it to trigger the code to run later. This worked, but it was messy. You had a timer to deal with, and you had to remember to disable it in the event handler, so it didn’t run multiple times. You also could have used a TThread, which may have been a better solution, but still seemed kind of messy, especially if you wanted to update the UI from your code.

Thanks to the new System.Threading library introduced with XE7, I’ve created a simple procedure that makes this a breeze to do. I call the procedure NonBlocking, but you could just as easily call it RunALittleLaterRunOutsideHandler, etc.

uses System.Threading;

procedure NonBlocking(const Proc: TThreadProcedure);
begin
  TTask.Create(procedure begin
    TThread.Queue(nil, Proc);
  end).Start;
end;

All this does is create a task, and then inside the task queue an update back to the main thread to execute the code that is passed to this procedure as an anonymous method. You could easily just write this code inline, but I thought it worthwhile creating a procedure to handle it for me.

Lets look at a normal execution scenario:

  //...
  ListBox1.Items.Add('Before Handler');
  EventHandler;
  ListBox1.Items.Add('After Handler');
  //....

procedure TForm1.EventHandler;
begin
  ListBox1.Items.Add('Inside Handler');
end;

When this is run, our ListBox1 will look like

  • Before Handler
  • Inside Handler
  • After Handler

which is what we would expect. Now when we introduce a call to our new procedure in the EventHandler:

  //...
  ListBox1.Items.Add('Before Handler');
  EventHandler;
  ListBox1.Items.Add('After Handler');
  //....

procedure TForm1.EventHandler;
begin
  ListBox1.Items.Add('Inside Handler 1');
  NonBlocking(procedure begin
    ListBox1.Items.Add('Outside Handler'); // This will run last
  end);
  ListBox1.Items.Add('Inside Handler 2');
end;

Our ListBox1 will look like

  • Before Handler
  • Inside Handler 1
  • Inside Handler 2
  • After Handler
  • Outside Handler

Notice that Outside Handler was the very last line added, even though it is written between Inside Handler 1 and Inside Handler 2. It even occurs after the After Handler line. Also, this works across all platforms: iOS, Android, Windows and OS X.

Everything before and within the call to NonBlocking will execute in order, but the code within NonBlocking will execute after the code that comes after that anonymous method.

If you have a ShowMessage or something else that blocks the UI thread in the event handler, then the code you passed to the NonBlocking procedure will be executed early, which is fine since the UI thread was already blocked.

 

7 replies on “Run Outside the Handler or Making a Non-Blocking Call”

I used to use messaging rather than a timer. Posting a message to the form and allowing a message proc to handle it after the fact had the same effect. Only potential issue was that messages were not guaranteed (although I rarely had a problem with missed messages in the past).

I do like the TThread.Queue method better if I’m targeting XE7+. Under the hood queue is a wrapper around synchronize, so that’s how its getting the thread safety.

Synchronize can lead to a deadlock and is less performant because it blocks. Queue move the code back to the main thread without a block. Additionally it better serves the purpose of this code.

Sometimes you need synchronize because it needs additional parameters being passed to the main thread. It used to be normal to pass these parameters as fields in the TThread derived class (or some form) and call synchronize to update the UI. Queue will not work because another call might overwrite your parameters. If you wanted to have real async, PostMessage was used with the parameters passed via lParam/wParam.

Comments are closed.