Получить результат функции асинхронно в Delphi, используя Omni Thread Library

Я пытаюсь вызвать функцию из другого модуля / класса, которая займет некоторое время при выполнении задачи и вернет строковое значение. Я не смог найти хорошую ссылку на что-то похожее на C# async-await, как простой подход в Delphi. Использование библиотеки Omni Thread кажется мне хорошей идеей.

Простой пример будет отличным началом для меня.

Примерный подход:

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;

Здесь функция GetMagicString должна обрабатывать результат асинхронно. Как только результат получен, только тогда программа должна уведомить, что задача была выполнена. Кстати, я использую Delphi-XE.

Edit1: вот что я пытался. Но я все еще не могу найти правильный способ сделать работу. Проблема в том, как вернуть значение.

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

Здесь с помощью Result:= ResultValue.Value оплачивается пользовательский интерфейс.

Edit2

Я внес изменения согласно предоставленному ответу.

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.

Код другого устройства: устройство 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.

Да, код работает как положено. Я просто хочу знать, является ли это лучшим способом сделать то, что я действительно хочу выполнить.

1 ответ

Решение

Обычно вы используете будущее в том случае, если вы хотите, чтобы что-то выполнялось в фоновом режиме, но все еще нуждалось в результате в том же пути выполнения. Это в основном позволяет вам делать что-то в фоновом режиме, в то время как вы делаете другое в основном потоке, и вы можете использовать результат фонового потока.

Вам нужно использовать асинхронную абстракцию, с которой связывается TLama:

В вашем случае это будет:

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;

Это немного грязно, но вы должны понять. Вы должны убедиться, что ваш асинхронный код завершен, прежде чем уничтожать форму, которой принадлежит этот код (TForm1).

Если вы хотите попробовать установить систему, которая будет вызывать событие после завершения кода, вы можете сделать что-то вроде этого:

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;

Затем вы можете поместить многопоточный код в модуль GetMagicString и просто вызвать вышеуказанный метод из вашей формы, передавая событие, которое будет вызвано после его завершения.

Другие вопросы по тегам