Retrofitting a classic

Posted by on in Blogs

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.

Tags: CodeGear


About
Gold User, Rank: 83, Points: 11

Comments

  • Guest
    Daniel Lehmann Monday, 8 September 2008

    That's just great!

  • Guest
    David Brennan Monday, 8 September 2008

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

  • Guest
    Jim Monday, 8 September 2008

    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.

  • Guest
    Jim Monday, 8 September 2008

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

  • Guest
    Bob Swart Monday, 8 September 2008

    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).

  • Guest
    Victor Monday, 8 September 2008

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

  • Guest
    ahmoy Monday, 8 September 2008

    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;

  • Guest
    Steve Summers Monday, 8 September 2008

    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.

  • Guest
    Allen Bauer Tuesday, 9 September 2008

    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.

  • Guest
    Allen Bauer Tuesday, 9 September 2008

    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.

  • Guest
    Allen Bauer Tuesday, 9 September 2008

    Bob,

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

    Allen.

  • Guest
    Allen Bauer Tuesday, 9 September 2008

    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.

  • Guest
    Serg Tuesday, 9 September 2008

    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?

  • Guest
    Allen Bauer Tuesday, 9 September 2008

    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.

  • Guest
    Chuck Jazdzewski Tuesday, 9 September 2008

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

    Chuck.

  • Guest
    Allen Bauer Tuesday, 9 September 2008

    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.

  • Guest
    Loïs Bégué Wednesday, 10 September 2008

    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;

  • Guest
    Peter Oosterweel Wednesday, 10 September 2008

    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

  • Guest
    Warren Thursday, 11 September 2008

    This is totally great! Much Kudos!

    W

  • Guest
    daniel Friday, 19 September 2008

    cool!
    d

  • Please login first in order for you to submit comments

Check out more tips and tricks in this development video: