3

I am trying to call a function from another unit/class which would take some time in performing the task and would return a string value. I couldn't find a good reference something similar to C# async-await like simple approach in Delphi. Using Omni Thread library seems a good idea for me.

A simple example will be a great start for me.

Sample approach:

procedure TForm1.button1Click(Sender: TObject);
begin
  // notify before starting the task
  memo1.Lines.Add('calling a asynchronous function..');

  // call to the function that takes some time and returns a string value
  memo1.Lines.Add(GetMagicString);

  // notify that the task has been completed
  memo1.Lines.Add('Results fetched successfully.');
end;

Here, the function GetMagicString should process the result asynchronously. Once the result is obtained, only then the program should notify that the task has been completed. By the way, I'm using Delphi-XE.

Edit1: Here is what I tried. But I am still unable to figure out the proper way to make the job done. The problem is how to return the value.

  .....
    private
      ResultValue: IOmniFuture<string>;
    .........
    .....


    function TForm1.FutureGet: string;
    begin
      Sleep(3000);
      Result := 'my sample magic string response ' +  IntToStr(Random(9999));
    end;

    procedure TForm1.FutureGetTerminated;
    begin
      // This code fired when the task is completed
      memo1.Lines.Add(ResultValue.Value);
    end;

    function TForm1.GetMagicString: string;
    begin
      ResultValue := Parallel.Future<string>(FutureGet,
            Parallel.TaskConfig.OnTerminated(FutureGetTerminated));

    end;

Here, using Result := ResultValue.Value feezes the UI.

Edit2

I made changes as per the answer provided.

MainForm Code: unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Unit2;



type
  TForm1 = class(TForm)
    memo1: TMemo;
    button1: TButton;
    procedure button1Click(Sender: TObject);
  private
    FOnStringReceived: TMyEvent;
    procedure StringReceived(const AValue: string);
    property OnStringReceived: TMyEvent read FOnStringReceived write FOnStringReceived;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TForm1.button1Click(Sender: TObject);
var
  MyObject: TMyClass;
begin
  // notify before starting the task
  memo1.Lines.Add('calling a asynchronous function..');

  // call to the function that takes some time and returns a string value
  MyObject := TMyClass.Create;
  OnStringReceived := StringReceived;
  try
    MyObject.GetMagicStringInBackground(OnStringReceived);
  finally
    MyObject.Free;
  end;
end;


procedure TForm1.StringReceived(const AValue: string);
begin
  memo1.Lines.Add(AValue);

   // notify that the task has been completed
  memo1.Lines.Add('Results fetched successfully.');
end;
end.

Other Unit Code: unit Unit2;

interface

uses SysUtils, OtlTask, OtlParallel, OtlTaskControl;

type
  TMyEvent = procedure(const aValue: string) of object;

type
  TMyClass = class
  private
    FOnStringReceived: TMyEvent;
    function GetMagicString: string;
  public
    procedure GetMagicStringInBackground(AEvent: TMyEvent);
end;

implementation

{ TMyClass }

function TMyClass.GetMagicString: string;
begin
  Sleep(3000);
  Result := 'my sample magic string response ' +  IntToStr(Random(9999));
end;

procedure TMyClass.GetMagicStringInBackground(AEvent: TMyEvent);
var
  theFunctionResult: string;
begin
  Parallel.Async(
    procedure
    begin
      theFunctionResult := GetMagicString;
    end,

    Parallel.TaskConfig.OnTerminated(
    procedure (const task: IOmniTaskControl)
    begin
      if Assigned(AEvent) then
        AEvent(theFunctionResult);
    end)
  );
end;
end.

Yes, the code works as expected. I just want to know if this is the best way of doing what I really want to perform.

Rabi Jayasawal
  • 441
  • 1
  • 9
  • 18

1 Answers1

5

You would normally use a future in a case where you want something executed in the background but still need the result in the same execution path. It basically lets you do something in the background while doing another thing in the main thread and you can then use the result of the background thread.

What you need to use is the Async abstraction that TLama linked to:

In your case it would be:

procedure TForm1.DoSomething;
var
  theFunctionResult: string;
begin
  memo1.Lines.Add('calling a asynchronous function..');
  Parallel.Async(
    procedure
    begin
      // executed in background thread
      theFunctionResult := GetMagicString;
    end,

    procedure
    begin
      // executed in main thread after the async has finished
      memo1.Lines.Add(theFunctionResult);

      // notify that the task has been completed
      memo1.Lines.Add('Results fetched successfully.');
    end
  );
end;

This is a bit messy but you should get the idea. You need to make sure that your async code is completed before you destroy the form that owns this code (TForm1).

If you want to try and setup a system that will call an event when the code completes then you can do something like this:

type
  TMyEvent = procedure(const aValue: string) of object;

procedure GetMagicStringInBackground(AEvent: TMyEvent);
var
  theFunctionResult: string;
begin
  Parallel.Async(
    procedure
    begin
      // executed in background thread
      theFunctionResult := GetMagicString;
    end,

    Parallel.TaskConfig.OnTerminated(
      procedure (const task: IOmniTaskControl)
      begin
        // executed in main thread after the async has finished
        if Assigned(AEvent) then
          AEvent(theFunctionResult );
      end
    )
  );
end;

You can then put the threaded code in the GetMagicString unit and just call the method above from your form passing in an event that will get called when it completes.

Graymatter
  • 6,529
  • 2
  • 30
  • 50
  • Did you mean I that must implement the threading part in the form unit to grab the task result? If there isn't something like awaiting for result in Delphi using OTL then I will be using this mechanism. – Rabi Jayasawal Sep 06 '15 at 10:27
  • @RabiJayasawal This is waiting for the result. The second anonymous method will only be executed when the threaded part is finished. The threading part can be put anywhere. The only reason why I put it in the form unit was because it's accessing the form variables (memo1). – Graymatter Sep 06 '15 at 18:07
  • As I previously stated, the function **GetMagicString** exists in an another unit. I need to call it from a form unit. The threading code will be on the same unit in which the function **GetMagicString** exists. Your code is fine. I just want to know if I can implement as asked. If NO is the answer, I am going to follow your method and accept your answer. – Rabi Jayasawal Sep 07 '15 at 08:05
  • @RabiJayasawal Take a look at the edit. That's probably what you are looking for. – Graymatter Sep 07 '15 at 08:49
  • Compiler throws an error. Btw I am a student and new in Threading and Event handling. So please ignore my noob skills. The error is: `[DCC Error] Unit2.pas(69): E2250 There is no overloaded version of 'Async' that can be called with these arguments` The error is actually thrown for the second parameter of **Parallel.Async** function. – Rabi Jayasawal Sep 07 '15 at 10:52
  • The library changed things. The new code should work. I haven't tested them in a compiler but it should be right. – Graymatter Sep 08 '15 at 06:14
  • I have posted my code. Please have a look at it. The code works fine. Is it the best way of implementing your sample code? – Rabi Jayasawal Sep 08 '15 at 08:08