使用 Omni 线程库在 Delphi 中异步获取函数结果

Get a function result asynchronously in Delphi using Omni Thread Library

我正在尝试从另一个 unit/class 调用一个函数,这将花费一些时间来执行任务并且 return 一个字符串值。我在 Delphi 中找不到类似于 C# async-await 之类简单方法的好参考。使用 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。

编辑1: 这是我试过的。但我仍然无法找出完成工作的正确方法。问题是如何return这个值。

  .....
    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 计算 UI.

编辑2

我根据提供的答案进行了修改。

主窗体代码: 单位 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.

是的,代码按预期运行。我只是想知道这是否是做我真正想做的事情的最佳方式。

如果您希望在后台执行某些操作但仍需要在相同的执行路径中执行结果,您通常会使用 future。它基本上可以让你在后台做一些事情,同时在主线程中做另一件事,然后你可以使用后台线程的结果。

您需要使用的是 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 单元中,只需从您的表单调用上面的方法,传递一个事件,该事件将在完成时调用。