Monday, September 8, 2008

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.

26 comments:

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

    ReplyDelete
  2. 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.

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

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

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

    ReplyDelete
  6. 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;

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

    ReplyDelete
  8. 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.

    ReplyDelete
  9. 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.

    ReplyDelete
  10. Bob,

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

    Allen.

    ReplyDelete
  11. 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.

    ReplyDelete
  12. 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?

    ReplyDelete
  13. 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.

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

    Chuck.

    ReplyDelete
  15. 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.

    ReplyDelete
  16. 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;

    ReplyDelete
  17. 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

    ReplyDelete
  18. This is totally great! Much Kudos!

    W

    ReplyDelete
  19. joe> what oveload man? the closure passes to synchronize exactly what synchronize always expected...

    ReplyDelete
  20. 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;

    ReplyDelete
  21. 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 ?

    ReplyDelete
  22. @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;

    ReplyDelete
  23. Thie would be the best solution:

    interface

    procedure foo; virtual; synchronized;

    implementation

    procedure foo;
    begin
    end;

    ReplyDelete
  24. I have this test, but this does not work with anonymous procedure, I get exception in the SetStr procedure, but with old style synchronize I don't receive exception!:

    type
    TProcTest = procedure(const TestStr: string) of object;

    TTestThread = class(TThread)
    private
    ProcTest: TProcTest;
    protected
    procedure Execute; override;
    end;

    procedure TTestThread.Execute;
    var
    S: string;

    procedure SetStr(I: Integer);
    begin
    S := 'Ok ' + IntToStr(I); // exception here, I can put any value to S
    end;

    var
    I: Integer;
    begin
    for I := 0 to 9 do begin
    SetStr(I);
    Synchronize(procedure begin
    ProcTest(S);
    end);
    end;
    end;

    procedure TForm2.Button1Click(Sender: TObject);
    begin
    with TTestThread.Create(True) do begin
    ProcTest := Self.ProcTest;
    FreeOnTerminate := True;
    Resume;
    end;
    end;

    procedure TForm2.ProcTest(const TestStr: string);
    begin
    Memo1.Lines.Add(TestStr);
    end;

    ReplyDelete

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.