如何使用AsyncCalls

时间:2016-05-21 13:08:24

标签: delphi nested-function

我在AsyncCalls 2.99

中使用modified version by Zarko Gajic获得了这段工作代码
function TForm1.DoIt(i:integer):integer;
begin
end;

procedure TForm1.Main;
  //-------------------------------------------------------
  procedure CallIt;
  begin
    TAsyncCalls.Invoke(
      procedure
      var i:integer;
      begin
        For i := 0 to 10 do
          If i < 11
            then TAsyncCalls.Invoke<integer>(DoIt,i));
    end);
  end;
  //-------------------------------------------------------
begin
  CallIt;
end;

现在我想将函数DoIt移动到Main作为CallIt旁边的嵌套函数:

procedure TForm1.Main;
  //-------------------------------------------------------
  function DoIt(i:integer):integer;
  begin
  end;
  //-------------------------------------------------------
  procedure CallIt;
  begin
    TAsyncCalls.Invoke(
      procedure
      var i:integer;
      begin
        For i := 0 to 10 do
          If i < 11
            then TAsyncCalls.Invoke<integer>(DoIt,i));
    end);
  end;
  //-------------------------------------------------------
begin
  CallIt;
end;

以上代码(自然)不起作用。尽管我unterstand Invoke需要一个方法作为参数,嵌套函数不是一个。

Invoke需要TAsyncCallArgGenericMethod:

class function Invoke<T>(Event: TAsyncCallArgGenericMethod<T>; const Arg: T): IAsyncCall; overload; static;

TAsyncCallArgGenericMethod<T> = function(Arg: T): Integer of object;

我已收到将TAsyncCallArgGenericMethod转换为引用的提示:

TAsyncCallArgGenericMethod<T> = reference to function(Arg: T): Integer;

虽然我有一般概念(即幻觉),但我理解这个概念,我无法生成工作代码。

3 个答案:

答案 0 :(得分:2)

  

现在我想将函数DoIt移动到Main作为CallIt旁边的嵌套函数:

您不能从包含它的函数外部调用嵌套函数 - 因为嵌套函数需要访问外部(包含)函数局部变量,这些函数仅在执行包含函数的代码时存在。

即使特定的嵌套函数没有评估它们访问这些局部变量的权限 - 它具有这些权限,编译器应该能够为此生成所有的低级脚手架。

特别是在您的代码段中,您无法从TForm1.Main.DoIt本身之外调用TForm1.Main。所以你不能把它引用并传递给像AsyncCall调度员这样的外部体。

它不取决于您是使用procedure of object还是reference to procedure还是任何其他类型 - 嵌套函数的基本属性是&#34;存在&#34;只在本地到包含函数,只有在外部函数运行时才能运行。 AsyncCall很可能会在TForm1.Main退出时尝试运行该函数,因此TForm1.Main.DoIt所需的局部变量堆栈框架将不存在。

你必须找到其他方式来打包&#34;打包&#34;这些功能在一起,嵌套函数不会在这里做。

例如,可以尝试使用高级记录。 尝试以某种方式安排它:

type
  TForm1 = class(TForm)
....
  private
    type Dummy = record
      procedure CallIt;
      procedure DoIt(const i:integer);
    end;
  end;

....

  //-------------------------------------------------------
  procedure TForm1.Dummy.CallIt;
  begin
    TAsyncCalls.Invoke(
      procedure
      var i:integer;
      begin
        For i := 0 to 10 do
          If i < 11
            then TAsyncCalls.Invoke<integer>(DoIt,i));
    end);
  end;
  procedure TForm1.Dummy.DoIt(const i:integer);
  begin
  end;

procedure TForm1.Main;
var d: Dummy;
begin
  d.CallIt;
end;

另外,我认为你的方法在这里是错误的:你会立即形成许多线程,耗尽你的操作系统资源。 我建议您使用OmniThreadLibrary,其中有高级Parallel-Loop和Collection-Pipeline概念。它们将为您提供自动线程池管理的好处,因此您只需拥有CPU可以承受的许多工作线程,使您的程序适应它将要运行的任何硬件。

答案 1 :(得分:1)

我也可能有一种错觉,我理解这些事情(也就是我可能是错的),所以请用一点点盐,但这是我对它的看法。

嵌套函数可以访问调用函数可用的所有参数(包括self),但没有“隐藏”参数(它不需要任何参数)。另一方面,类函数有一个隐藏参数(称为“self”),函数访问该参数以查找实际调用该函数的对象。因此签名完全不同。

如果你回到过去C ++是一个解释器的时代,像C ++中的Fred.Main(x,y)这样的东西会被翻译成像C中的Main(Fred,x,y)这样的东西。我只包括这个说明隐藏参数的工作原理。

所以结果就是你无法做你想要做的事情,因为通过在你的Main函数中移动DoIt,你完全改变它的签名,以及它的工作原理。

答案 2 :(得分:0)

我真的不能离开它,因为出于某种原因,我真的把它咬进了它。现在,这是一个解决方案。不是我推荐的解决方案,而是解决方案。

大约4年前,有关stackoverflow的讨论here。大卫引用了documentation并继续说道:

  

如果我没记错的话,一个额外的隐藏参数会被传递给嵌套函数,并带有指向封闭堆栈帧的指针。如果没有引用封闭环境,则在32位代码中省略。

SertaçAkyüz显然在汇编代码中喋喋不休地报道:

  

这是一个隐含的参数好吧!编译器假定它在'rcx'中有它的东西,函数的参数在'rdx'和'r8',而实际上没有'它的东西',参数在'rcx'和'rdx'。 / p>

这似乎完成了整个事情。

但是,有这样的文字:How to pass a nested routine as a procedural parameter (32 bit)。如果您考虑文档,这是一个相当令人惊讶的标题。这导致了以下代码:

{unit AsyncCalls;}
TAsyncCalls = class(TObject)
private
  type
    …
    //TAsyncCallArgGenericMethod<T> = function(Arg: T): Integer of object;
    TAsyncCallArgGenericMethod<T> = reference to function(Arg: T): Integer;

uses … ,AsyncCalls,AsyncCallsHelper;

procedure TForm1.Main;
  //-------------------------------------------------------
  function DoIt(i:integer):integer;
  begin
    Result := i;
  end;
  //-------------------------------------------------------
  procedure CallIt;
  var p:Pointer;
  begin
    p := @DoIt;
    TAsyncCalls.Invoke(
      procedure
      var i:integer;
      begin
        For i := 0 to 10 do
          If i < 11 then
            AsyncHelper.AddTask(TAsyncCalls.Invoke<integer>(p,i));
    end);
  end;
  //-------------------------------------------------------
begin
  CallIt;
end;

此代码有效。正如我之前提到的,我不建议使用它,但它有效。我在寻找解决方案的过程中学到了很多东西,我现在认为这是一个主要的好处。