Cross platform anonymous threads and progress notification

Posted by on in Blogs
Reading Anders Ohlsson's latest blog post about using iOS APIs we don't wrap reminded me of another couple of helper classes that I created, and which ship with the RAD Studio XE4 samples.

The first of these is the TAnonymousThread<T> generic class (in <samples installation directory>Delphi\RTL\CrossPlatform Utils\AnonThread.pas), and is designed to make it easy to create and consume anonymous threads for any activities that will return some kind of result (e.g. fetching data from a remote service). The public API for it is as follows :-
TAnonymousThread<T> = class(TThread)
public
  constructor Create(AThreadFunc: TFunc<T>; AOnFinishedProc: TProc<T>;
    AOnErrorProc: TProc<Exception>; AStartSuspended: Boolean = False;
    AFreeOnTerminate: Boolean = True);
end;

As you can see, the interface is pretty straightforward. It takes a series of procedure and function pointers to allow you to specify a function to be run in the thread, a callback procedure which will be run in the main thread so you can process the result, and a callback procedure which will run in the main thread if an exception occurs during thread processing. By default it will not start the thread suspended, and will free the thread on termination (for platforms that don't have ARC implementations).

Here is an example of using this class:-
var
  lThread: TAnonymousThread<TDataSet>;
begin
  lThread := TAnonymousThread<TDataSet>.Create(
    function: Boolean
    begin
      //Runs in separate thread
      Result := SomeLongRunningMethodReturningADataSet;
    end,
    procedure(AResult: TDataSet)
    begin
      //Runs in main thread
      SomeMethodToProcessDataInMainThread(AResult);
    end,
    procedure(AException: Exception)
    begin
      //Runs in main thread
      ShowMessage(AException.Message);
    end);
end;

The second class is the TAsyncProgress<T> generic class (in <samples installation directory>Delphi\RTL\CrossPlatform Utils\Xplat.Utils.pas). Like the TAnonymousThread<T> class, it is designed to run a method returning a result in a separate thread, but has the added functionality of showing notification of a long running process. The public API for this class is as follows:-
TAsyncProgress<T> = class
  public
    class procedure Execute(AFunc: TFunc<T>; AOnFinished: TProc<T>;
      AOnError: TProc<Exception>);
  end;

As you can see, it has a similar interface to TAnonymousThread<T>, and accepts callback methods to handle a threaded function, and methods running in the main thread to process the result, or any exception that may occur during thread processing.

Here is an example of using this class:-
TAsyncProgress<Boolean>.Execute(
  function: TDataSet;
  begin
    //Runs in separate thread
    Result := SomeMethodReturningADataSet;
  end,
  procedure(AResult: TDataSet)
  begin
    //Runs in main thread
    SomeMethodToProcessData(AResult);
  end,
  procedure(AException: Exception)
  begin
    //Runs in main thread
    ShowMessage(AException.Message);
  end);

If you look at the implementation of TAsyncProgress<T> in Xplat.Utils.pas, you'll see that it leverages the FMX.Platform.TPlatformServices class to register and consume an IPleaseWaitService interface. This interface has the following signature:-
IPleaseWaitService = interface
  procedure StartWait;
  procedure StopWait;
end;

I have created an implementation of this interface for iOS in iOS.Services.pas, and for OSX in Mac.Services.pas. The iOS implementation uses the technique that Anders blogged about to show the network activity indicator in the iOS status bar, but it also uses the UIActivityIndicatorView class to show a spinning progress indicator in the centre of your application view. Similarly the OSX implementation uses the NSProgressIndicator class to show a spinning progress indicator in the center of your main OSX application view.

To register an implementation for a given platform, simply add the relevant *.Services.pas file to your project.

In addition to leveraging the IPleaseWaitService interface via the TAsyncProgress<T> class, you can also interact with this service directly using helper methods in XPlat.Utils.pas, by utilizing the following:-
//Returns an instance of the IPleaseWaitService registered for the current platform
function PleaseWaitService: IPleaseWaitService;

//Runs the specified procedure (unthreaded), using the IPleaseWaitService interface to
//indicate execution progress.
procedure ProgressProc(AProc: TProc);

//Wraps a call to IPleaseWaitService.StartWait, if the service has been registered
procedure StartWait;

//Wraps a call to IPleaseWaitService.StopWait, if the service has been registered
procedure StopWait;

There are sample applications for iOS showing how to consume the TAnonymousThread<T> and TAsyncProgress<T> classes located in <samples base>Delphi\RTL\CrossPlatform Utils\AsyncProgress/AnonymousThread.dproj and <samples base>Delphi\RTL\CrossPlatform Utils\AsyncProgress/AsyncProgress.dproj respectively.
About
Gold User, Rank: 75, Points: 20
Software developer, family man, All Blacks fan, Playstation junkie, and headbanger since ages ago.
Comments are not available for public users. Please login first to view / add comments.