Retrofitting a classic

When Delphi 2 was released targeting the 32bit Windows API there were some new-to-Delphi features of the operating system that opened up some new possibilities; Pre-emptive multi-tasking and multi-threading. Coupled with this "new" concept of a "thread," Delphi introduced the new TThread class that was an abstract base class from which one would derived in order to "wrap" an operating system thread. Along with this new functionality, a rather contrived demo was also introduced that showcased this overall notion of "multi-threaded" programming by visually representing the speed differences between three different sorting algorithms, the Bubble-Sort, the Selection-Sort and the Quick-Sort. That demo has remained virtually unchanged ever since. One thing this demo also showed was a way to update the UI by "synchronizing" the sorting thread with the main, or UI thread. This ensured that any UI updates occurred only on the UI thread and only when the UI thread was ready. Even though this is not the best technique in terms of performance, it is safe.

This synchronization was accomplished through the use of the Synchronize method on TThread which took a parameterless method as the parameter which would then block the calling thread, switch to the main, or UI thread, call the method and then return. Since this method took no parameters it was often very tedious to pass information from the running thread over to the UI thread. As the thread demo showed, this involved communicating the parameter data with the foreground by storing this data in fields on the TThread descendant instance. This worked fine even though it was cumbersome.

Fast-forward to now. Delphi 2009 was recently announced and in fact just went "gold" yesterday, Sunday, September 7th, 2008 at around 4pm PDT. One of the more exciting language features to be included in this release aside from generics, is anonymous methods or more accurately, closures. The reason we call them anonymous methods is two-fold. The corresponding concept in .NET is also called an anonymous methods and in C++Builder a method pointer is already declared using the extended __closure keyword syntax. Rather than introduce confusion among both Delphi and C++Builder customers we opted for the "Anonymous Method" moniker. However, for those computer science purists, you can most certainly think of them as true closures, but I digress :-). Because of this new-fangled anonymous method thingy, a new Synchronize overload was added to TThread that now takes a parameterless anonymous method. Here’s the old code in the thread demo that would update the UI:

{ Since DoVisualSwap uses a VCL component (i.e., the TPaintBox) it should never
  be called directly by this thread.  DoVisualSwap should be called by passing
  it to the Synchronize method which causes DoVisualSwap to be executed by the
  main VCL thread, avoiding multi-thread conflicts. See VisualSwap for an
  example of calling Synchronize. }

procedure TSortThread.DoVisualSwap;
begin
  with FBox do
  begin
    Canvas.Pen.Color := clBtnFace;
    PaintLine(Canvas, FI, FA);
    PaintLine(Canvas, FJ, FB);
    Canvas.Pen.Color := clRed;
    PaintLine(Canvas, FI, FB);
    PaintLine(Canvas, FJ, FA);
  end;
end;

{ VisusalSwap is a wrapper on DoVisualSwap making it easier to use.  The
  parameters are copied to instance variables so they are accessable
  by the main VCL thread when it executes DoVisualSwap }

procedure TSortThread.VisualSwap(A, B, I, J: Integer);
begin
  FA := A;
  FB := B;
  FI := I;
  FJ := J;
  Synchronize(DoVisualSwap);
end;

Notice that it takes two methods, the one called from within the thread and then the one that is "synchronized" with the UI thread. You need to declared these methods on the class, which can sometimes be tedious. Also, this code requires manual assignment of the instance fields from the parameters. What if you could just pass in the code to synchronize along with the local state? With anonymous methods this is easy. Here’s the above code changed to use an inlined anonymous method:

procedure TSortThread.VisualSwap(A, B, I, J: Integer);
begin
  Synchronize(procedure
   begin
     with FBox do
     begin
       Canvas.Pen.Color := clBtnFace;
       PaintLine(Canvas, I, A);
       PaintLine(Canvas, J, B);
       Canvas.Pen.Color := clRed;
       PaintLine(Canvas, I, B);
       PaintLine(Canvas, J, A);
     end;
   end);
end;

By using an anonymous method, I’ve eliminated the DoVisualSwap method and removed the need for the FA, FB, FI, and FJ instance fields. Once you get used to the new syntax, this code is much easier to understand an use.

Posted by Allen Bauer on September 8th, 2008 under CodeGear |



30 Responses to “Retrofitting a classic”

  1. Daniel Lehmann Says:

    That’s just great!

  2. David Brennan Says:

    Awesome… lots of nice new features, now I just hope Delphi 2009 is fast and stable! ;-)

  3. Jim Says:

    I’m not sure I see the point of this (maybe it’s the example used). If I want to do the above several times in different places. The approach used first would seem better. If anonymous methods are only useful as a one shot, then they are not too useful at all. It also precludes the separation of implementation and specification.

  4. Xepol Says:

    The quicksort demo isn’t the only demo that hasn’t changed since Delphi 2.

    I’m just sayin… Best foot forward over a decade ago, might just be wearing a mighty scuffed up shoe nowaways, ya know?

    Maybe that’s why the demos are now next to impossible to find?

    Maybe a nice page in the welcome section that at least had nice HTML markup describing each demo and what it did and then let people load it up easily… Might reduce that new user curve maybe?

    Sorry, I know, that really wasn’t what your post was about, and the anonymous sync stuff is a nice touch. Maybe if there was a way to find the demo and a description about in the.. err, sorry folks, a bit of backsliding there.

  5. Jim Says:

    Oops, I see part of the point. But I’m still not convinced.

  6. Bob Swart Says:

    Nice example, also good to demo "live", since the actual thrddemo project that ships with Delphi 2009 appears to use the old way (so the project can be used as example to modify in order to use anonymous methods).

  7. Victor Says:

    Nice. Did you also add an overloaded Sort method to TList that accepts an anonymous compare function?

  8. ahmoy Says:

    Can the anonymous method passed to Synchronize method
    access any class variable? most likely it can’t since
    the code generated will not push self pointer.

    procedure TSortThread.VisualSwap(A, B, I, J: Integer);
    begin
    Synchronize(procedure
    begin
    // accessing a class variable…
    if (FSomething > 0) then
    begin
    // do something…
    end;
    end);
    end;

  9. Steve Summers Says:

    Speaking of the demos….
    You know, many of us in the Delphi community would be willing to help make the "Delphi experience" better for new users, to grow the user base.

    Would it make sense for CodeGear to run a contest for best replacement for the existing demo programs (and maybe some new areas too)? I’ll bet most of us would contribute something for a T-Shirt or hat (to replace our obsolete Borland ones!).

    If you want to be cheap, contest winners could get a free "Software Assurance" subscription for a year. That generally ends up not costing you anything anyway, since the releases have been just over a year apart. (Note the lack of a smiley there.)

    Or if you want to be nice, maybe a free license for a pro version of Delphi or C++ builder.

  10. Allen Bauer Says:

    ahmoy,

    If you mean instance variables, yes you can access them. In fact the demo I presented does. The "FBox" variable in the "with" statement is an instance variable. The same for class variables, which are really global variables scoped to the class type.

    Allen.

  11. Allen Bauer Says:

    Victor,

    Not to the existing TList, no. We have, however, introduced a complement of generic classes that do allow you to pass in an anonymous method for the compare function. In Generics.Collections.pas there is a TList<T> which allows you to create a TList containing any type.

    Allen.

  12. Allen Bauer Says:

    Bob,

    Yep, you’re right. In fact I did precisely this demo for the online demo of generics and anonymous methods.

    Allen.

  13. Allen Bauer Says:

    Jim,

    I could have recoded the VisualSwap function to directly access the FBox field, then replaced the call to VisualSwap with a call Syncronize() with an anonymous method that simply turns around and calls VisualSwap() directly. It would be the same effect.

    The whole point of an anonymous method is that it automatically "closes around" the surrounding state so you don’t have to jump through some of these hoops. You can also think of them as nested functions/procedures that can occur right "in-place."

    Allen.

  14. Serg Says:

    Good example, but it is interesting to know the "insides" of Delphi closure (anonimous method). What is it on binary level? What "calling conventions" for anonimous methods are?

  15. Allen Bauer Says:

    Serg,
    You should follow Barry Kelly’s blog for more information about the internal implementation of a closure and what the compiler does to create this "magic." In future posts, Barry is planning on presenting some of the behind the scenes implementation of anonymous methods. http://barrkel.blogspot.com

    Allen.

  16. Chuck Jazdzewski Says:

    This was want I wanted to write to begin with. I am happy to see you now can. Congratulations on the new release!

    Chuck.

  17. Allen Bauer Says:

    Hey Chuck!

    Thanks! Yeah, Anonymous Methods are going to open up a whole new world of programming idioms and paradigms. I think I like them just a little bit more than generics :-).

    Allen.

  18. Nick Hodges » Blog Archive » Random Thoughts on the Passing Scene #82 Says:

    [...] Allen writes a great article on a cool new use for Anonymous Methods in TThread. [...]

  19. Loïs Bégué Says:

    Nice "technology review"…

    In that very specific case (!!), I’d suggest the use of a "KISS" convenient way :) avoiding the use of a classic "synchronize" call:

    procedure TSortThread.VisualSwap(A, B, I, J: Integer);
    Begin
    with FBox do
    try
    Canvas.LOCK;

    Finally
    Canvas.UNLOCK;
    End;
    End;

    I can’t identify any drawback …

    Alternative: Canvas.TRYLOCK;

  20. Peter Oosterweel Says:

    Briljant! :) Guess the debugger steps through the statements as one might expect? Question: do anonymous methods support things like nested functions or even more declarations?

    Another nice-to-have would be string-based methods with the anonymous-syntax:
    procedure TSortThread.VisualSwap(A, B, I, J: Integer);
    begin
    Synchronize( ‘procedure ‘
    + ‘begin ‘
    + ‘ with FBox do ‘
    + ‘ begin ‘
    + ‘ Canvas.Pen.Color := clBtnFace; ‘
    + ‘ PaintLine(Canvas, I, A); ‘
    + ‘ PaintLine(Canvas, J, B); ‘
    + ‘ Canvas.Pen.Color := clRed; ‘
    + ‘ PaintLine(Canvas, I, B); ‘
    + ‘ PaintLine(Canvas, J, A); ‘
    + ‘ end; ‘
    + ‘end;’);
    end;
    Yes, I know this would involve JIT-compiling, but that’s exactly my point. Scripting has proven to be a way to popularize languages and right now it’s extremely cumbersome to build in scripting in a Delphi-application. Please please please make this a feature in Delphi asap.

    Btw, thanks for your article,
    Groetjes, Peter

  21. Warren Says:

    This is totally great! Much Kudos!

    W

  22. Joe White Says:

    I’m puzzled. Why do you need a different overload of Synchronize to take a closure? Are closures represented by something other than the good-old tried-and-true TMethod? And if so, what’s the syntax for dealing with them?

  23. daniel Says:

    cool!
    d

  24. daniel Says:

    joe> what oveload man? the closure passes to synchronize exactly what synchronize always expected…

  25. Joe White Says:

    Daniel, did you read the article? Allen said, "a new Synchronize overload was added to TThread that now takes a parameterless anonymous method."

    So no, the closure does not pass to Synchronize exactly what Synchronize always expected. If it did, they wouldn’t have needed to add an overload.

    And that prompted my question: is it not just a procedure of object? And if not, what’s the syntax?

  26. The Oracle at Delphi » A Sink Programming. Says:

    [...] In this post I demonstrated how you can use an Anonymous Method to "synchronize" a background thread with the main UI thread. However there are times where you don’t want to block execution of the thread but still want to have something happen in the main UI thread, asynchronously. For several releases of Delphi there has been the Queue() method on TThread. This allows you to schedule a TThreadMethod (just like Synchronize) to execute on the main UI thread. The difference is that Synchronize will block the caller until the UI thread completes the call, while Queue will return immediately. The problem with the Queue() method is that there is no simple way to know when the "queued" event is done executing. Queue() really only works in a "queue it and forget it" scenario. It is still a form of Async programming has limited usefulness. New to D2009, a new Queue() overload was added just like Synchronize() that takes "reference to procedure" type (the underlying type to which you can assign an Anonymous Method). Let’s see if we can use the Queue() method as the underlying mechanism for doing some "A Sink" programming :-). [...]

  27. Bart Roozendaal Says:

    I agree, the way synchronize works/worked was tedious and this example will make the work of the programmer a bit easier.

    I think it is noticeble that the examples of anonymous methods that make sense (or at least to me) all have to do with threads and the like. The anonymous methods in these examples are all used to create some sort of condition before executing code.

    One begins to wonder: maybe there would be a (greater) need for a synchronize statement. If called from the UI thread, nothing happens. If called from another thread, the Synchronize mechanism kicks in…

    procedure Foo;
    begin
    BeginSynchronize;
    try
    FBox.Canvas.Pen.Color := clBtnFace;
    FBox.PaintLine(Canvas, FI, FA);
    FBox.PaintLine(Canvas, FJ, FB);
    FBox.Canvas.Pen.Color := clRed;
    FBox.PaintLine(Canvas, FI, FB);
    FBox.PaintLine(Canvas, FJ, FA);
    finally
    EndSynchronize;
    end;
    end;

  28. Per Bakkendorff Says:

    Bart - I agree with you. I sure they (the CodeGear guys) have something in the pipeline for simplifying use of multiple threads.

    How about a keyword in declaration instead like:
    procedure Foo; syncronized;
    begin
    // do some UI thing in the main thread…
    end;

    I sure that the folks in the R&D group can do that compiler magic just to follow your example, right ?

  29. Bart Roozendaal Says:

    @Per, that’s an even better solution I think. But I guess that synchronized keyword should be added in the prototype of the function, not in the implementation.

    interface

    procedure foo; virtual; synchronized;

    implementation

    procedure foo;
    begin
    end;

  30. Paweł Głowacki : Delphi 2009 Online Resources Says:

    [...] Allen Bauer blog post: "Retrofitting a classic" TThread demo with anonymous methods [...]

Leave a Comment


Server Response from: dnrh1.codegear.com

 
© Copyright 2008 Embarcadero Technologies, Inc. All Rights Reserved. Contact Us  |   Site Map  |   Legal Notices  |   Privacy Policy  |   Report Software Piracy