Saturday, February 21, 2015

Type less - how to use GExperts macro templates

With all these articles and presentations about the PPL you might have seen this often. Calling TThread.Queue (or TThread.Synchronize if you are doing it wrong ;)) to execute your VCL or FMX related code in the main thread.

Are you still typing this code wasting precious time? Well then this article is for you!

I see this so often in presentations by people that should know better given their many years of Delphi experience. And to be honest - I am guilty of that too. I could spend way less time writing stupid code (with stupid code I usually refer to boilerplate code like this).

Disclaimer: I tried using live templates also but man that sucked and invoking the macro always killed the selection in the code editor.

Open the GExperts macro templates (default shortcut is Shift+Alt+T) and click on configuration.


After you click OK you enter the following code into the editor (with a trailing line break):

TThread.Queue(nil,
  procedure
  begin
    %SELECTION%|
  end);

So it looks like this:


The %SELECTION% tells the macro to insert the selected text here and the pipe tells it to set the cursor to this position after.

Click on OK now and select some source you want to invoke in the main thread.
Press the shortcut for macro templates, type queue and press enter, boom, done.

More time for awesome code or creating more of these templates!
(or to watch funny clips on the internet ...) ;)

Extending the parallel programming library

Recently Robert Love blogged about Exception Management in the PPL (let's please stick to this abbreviation since that is what Embarcadero calls it).

What I was missing though was handling exceptions in fire and forget tasks since you usually don't have some place that calls wait on them just to get the exception being raised.

So I quickly hacked together some stuff to show how to use a feature from the TLP (that's the .NET one): ContinueWith. From its documentation:

Creates a continuation that executes asynchronously when the target Task completes.

Easy enough. We got all the pieces to create this - how this can be done has been shown previously. Too bad he did not make a method of it but hacked it all into a button click event. :(

Did I just hear anyone say: "Hey, that would have been a use-case for an interface helper, right!?" back there? Well, you are right...

Enough talk - let's look at the code. Keep in mind this is just some quick and dirty example to show how to extend TTask in a clean way to add new features. Let's hope they will come out of the box with the next version because continuations on tasks are a must have imho.

unit ThreadingEx;

interface

uses
  SysUtils,
  Threading;

type
  TAction<T> = reference to procedure(const arg: T);

  TTaskContinuationOptions = (
    NotOnCompleted,
    NotOnFaulted,
    NotOnCanceled,
    OnlyOnCompleted,
    OnlyOnFaulted,
    OnlyOnCanceled
  );

  ITaskEx = interface(ITask)
    ['{3AE1A614-27AA-4B5A-BC50-42483650E20D}']
    function GetExceptObj: Exception;
    function GetStatus: TTaskStatus;
    function ContinueWith(const continuationAction: TAction<ITaskEx>;
      continuationOptions: TTaskContinuationOptions): ITaskEx;

    property ExceptObj: Exception read GetExceptObj;
    property Status: TTaskStatus read GetStatus;
  end;

  TTaskEx = class(TTask, ITaskEx)
  private
    fExceptObj: Exception;
    function GetExceptObj: Exception;
  protected
    function ContinueWith(const continuationAction: TAction<ITaskEx>;
      continuationOptions: TTaskContinuationOptions): ITaskEx;
  public
    destructor Destroy; override;

    class function Run(const action: TProc): ITaskEx; static;
  end;

implementation

uses
  Classes;

{ TTaskEx }

function TTaskEx.ContinueWith(const continuationAction: TAction<ITaskEx>;
  continuationOptions: TTaskContinuationOptions): ITaskEx;
begin
  Result := TTaskEx.Run(
    procedure
    var
      task: ITaskEx;
      doContinue: Boolean;
    begin
      task := Self;
      if not IsComplete then
        DoneEvent.WaitFor;
      fExceptObj := GetExceptionObject;
      case continuationOptions of
        NotOnCompleted:  doContinue := GetStatus <> TTaskStatus.Completed;
        NotOnFaulted:    doContinue := GetStatus <> TTaskStatus.Exception;
        NotOnCanceled:   doContinue := GetStatus <> TTaskStatus.Canceled;
        OnlyOnCompleted: doContinue := GetStatus = TTaskStatus.Completed;
        OnlyOnFaulted:   doContinue := GetStatus = TTaskStatus.Exception;
        OnlyOnCanceled:  doContinue := GetStatus = TTaskStatus.Canceled;
      else
        doContinue := False;
      end;
      if doContinue then
        continuationAction(task);
    end);
end;

destructor TTaskEx.Destroy;
begin
  fExceptObj.Free;
  inherited;
end;

function TTaskEx.GetExceptObj: Exception;
begin
  Result := fExceptObj;
end;

class function TTaskEx.Run(const action: TProc): ITaskEx;
var
  task: TTaskEx;
begin
  task := TTaskEx.Create(nil, TNotifyEvent(nil), action, TThreadPool.Default, nil);
  Result := task.Start as ITaskEx;
end;

end.

So what I did is add the ContinueWith method here that takes the delegate that gets executed when the previous task finished with a certain state. I also added properties for the Status and the Exception that might have been raised.

How can this be used?

  TTaskEx.Run(
    procedure
    begin
      Sleep(2000);
      raise EProgrammerNotFound.Create('whoops')
    end)
    .ContinueWith(
    procedure(const t: ITaskEx)
    begin
      TThread.Queue(nil,
        procedure
        begin
          ShowMessage(t.ExceptObj.Message);
        end);
    end, OnlyOnFaulted);

This executes the first task which Sleeps 2 seconds and then raises an exception. This would not only leak memory (!) but also there is no possibility to handle this exception unless you keep a reference to this task and then call Wait somewhere. But this would limit its use very much. Instead we call ContinueWith now passing the error reporting delegate and OnlyOnFaulted in order to execute this only if the previous task had an error.

Easy enough, isn't it?

Saturday, February 7, 2015

TestInsight - unit testing like a pro

The time of teasing is finally over - today I want to show you what I have been working on over the past few weeks:

TestInsight is an IDE plugin for Delphi (supporting XE and up) that will improve your unit test experience. It integrates nicely into your IDE removing the necessity to deal with some external test runner. It runs and presents the results on demand, when saving or continuously whenever you change the code.

Navigating to any test is just a matter of double clicking the test in the results overview.

As I would like you to experience it yourself here is a quick introduction:

After you downloaded and installed TestInsight you can access it from the Delphi main menu ("View -> TestInsight Explorer").

To get your test project running with TestInsight you have to add the TESTINSIGHT define to your project - you can do that by context menu in the project manager:


The second step will be to add the TestInsight client unit to your project. Currently supported frameworks are DUnit, DUnit2 and DUnitX. So let's say you have a project that is using DUnit you have to add the unit TestInsight.DUnit to your project. Calling RunRegisteredTests (as you know from DUnit) of that unit will execute your tests using TestInsight.

So a very basic project file will then look like this:

program MyTests;

uses
  TestInsight.DUnit,
  MyTestCase in 'MyTestCase.pas';

begin
  RunRegisteredTests;
end.

After building your project and hitting run it will report the results to the plugin. That will enable you to run your tests on remote machines or even mobile devices.


Hopefully you don't have errors or warnings in your results. ;) But if you do navigating to the source is just a double click (if you are using madExcept, JclDebug or similar libraries you can even navigate right to the line that caused the error - look into the TestInsight.Client unit on how to enable that)

For an even better TDD experience you can select the clock or disk icon to execute the tests whenever you change the code (after a small configurable idle time) or save it. You can completely focus on the TDD cycle without being interrupted by running the tests manually.


Download the setup here. For more information or reporting issues please visit the official project page.

And also many thanks to my closed beta testers and coworkers that helped me finding many bugs during development.