0

I have this piece of working code using AsyncCalls 2.99 in the 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;

Now I would like to move the function DoIt into Main to be a nested function next to 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;

The above code (naturally) does not work. As much as I unterstand Invoke requires a method as parameter and a nested function isn't one.

Invoke expects a TAsyncCallArgGenericMethod:

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

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

I have already received a hint to convert the TAsyncCallArgGenericMethod into a reference:

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

Although I have the general notion (i.e. illusion) that I understand the concept I have not been able to produce working code.

stackmik
  • 151
  • 1
  • 10
  • You could read the documentation for anon methods – David Heffernan May 21 '16 at 13:10
  • If all coding problems could be solved by reading the documentation SO would not exist. As I wrote, I have a working solution, so that is _not_ the problem. The fact that despite extensive reading and trying I'm not able to produce working code shows me that I have _not_ understood some advanced concepts in Delphi. But I would like to. Looking at the questions being asked here at SO I am probably not the only one. – stackmik May 21 '16 at 13:38
  • I thought your problem was not understanding anon methods. What is the specific question that you ask? – David Heffernan May 21 '16 at 13:56
  • As much as I understood Invoke expects a method. TForm1.DoIt is a method, a nested function is not, so it won't work. By changing "function" to "reference to function" you should be able to pass an anonymous method. I have tried to assign TAsyncCallArgGenericMethod to a variable, but no avail. No matter what I did, the compile would not recognize TAsyncCallArgGenericMethod, even though I made it public. Then I somehow got lost in the jungle of references. – stackmik May 21 '16 at 14:15
  • I can't be sure I know what you did. If you showed what you tried rather than describing it that would help. – David Heffernan May 21 '16 at 14:17
  • Very sorry, I wanted to include code, but it doesn't appear properly formatted although I added 4 spaces in front of every line. How do I go about this? Plus the code exceeds the character limitation. – stackmik May 21 '16 at 14:37
  • I have edited the question to make it more clear, I hope, I haven't messed it up even more. – stackmik May 21 '16 at 14:50
  • You can not call nested function from outside the function containing it - because nested functions need to access the outer(containing) function local variables, that only exists while executing code inside that containing function. You can not call `TForm1.Main.DoIt` from outside of `TForm1.Main`. So you can not take the reference to it and pass it to some external body like AsyncCall dispatcher. – Arioch 'The May 21 '16 at 14:54
  • what Delphi version u use? please add the proper tag to your question – Arioch 'The May 21 '16 at 15:00

3 Answers3

2

Now I would like to move the function DoIt into Main to be a nested function next to CallIt:

You can not call nested function from outside the function containing it - because nested functions need to access the outer(containing) function local variables, that only exists while executing code inside that containing function.

Even if the particular nested function does not evaluate their rights of accessing those local variables - it has those rights and the compiler should be able to produce all the lo-level scaffolding for that.

Specifically in your snippet, You can not call TForm1.Main.DoIt from outside of the TForm1.Main itself. So you can not take the reference to it and pass it to some external body like AsyncCall dispatcher.

It does not depend upon whether you would use procedure of object or reference to procedure or any other type - it is the fundamental property of nested function that they "exist" only locally to the containing function and only can be run when the outer function runs. AsyncCall would most probably try to run the function when TForm1.Main would be exited and thus its local variables stack frame required by TForm1.Main.DoIt would not exist.

You have to find some other way to "pack" those functions together, nested functions would not do here.

For example one may try using Advanced Records here. Try to arrange it somehow like that:

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;

Also, I think your approach is wrong here: you would instantly form many-many threads exhausting your OS resources. I would suggest you using OmniThreadLibrary instead, where there are hi-level Parallel-Loop and Collection-Pipeline concepts. They would give you benefit of automatic threads pool management, so you would only have so many worker threads as your CPU can bear, adapting your program to any hardware it would happen to run on.

Arioch 'The
  • 15,799
  • 35
  • 62
  • I'm not sure if we understand each other sufficiently. The first part of my code works without problems. There is no problem calling a method of TForm1 from AsyncCalls. And AsyncCalls _is_ a thread pool. I just need some metamorphosis of the nested function DoIt to make it callable by AsyncCalls. – stackmik May 21 '16 at 15:30
  • 1
    yes, you can call any **regular** function like `TForm1.Main` from external code. But you can not call any **nested** functions from external code, and I explained you why it is not technically possible. You need to transform `TForm1.Main.DoIt` so it would be come **regular** not **nested** and I showed one of many approaches how one can do it. – Arioch 'The May 22 '16 at 13:38
1

I may also have the illusion that I understand these things (i.e. I may be wrong) so take this with a pinch of salt, but this is my take on it.

A nested function has access to all parameters available to the calling function (including self), but has no 'hidden' parameters (it doesn't need any). The class function on the other hand has a hidden parameter (called 'self') that the function accesses to find the object that is actually calling the function. Thus the signatures are totally different.

If you go back to the olden days when C++ was an interpreter, something like Fred.Main( x, y) in C++ would be translated to something like Main( Fred, x, y) in C. I only include this to illustrate how that hidden parameter works.

So the upshot is you can't do what you are trying to do because by moving DoIt inside your Main function, you are completely changing its signature, and indeed how it works.

Dsm
  • 5,870
  • 20
  • 24
0

I just couldn't leave it at that since for some reason I really had sunk my teeth into it. Now, here's a solution. Not a solution I would recommend, but a solution.

There has been a discussion here on stackoverflow some 4 years ago. David quoted the documentation and continued:

If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment.

Sertaç Akyüz apparently poked around in the assembler code and reported:

It's an implicit parameter alright! The compiler assumes it has its thing in 'rcx' and the parameters to the function are at 'rdx' and 'r8', while in fact there's no 'its thing' and the parameters are at 'rcx' and 'rdx'.

This seemed to finish the whole thing.

But then, there is this text: How to pass a nested routine as a procedural parameter (32 bit). A rather surprising title if you consider the documentation. This led to the following code:

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

This code works. As I mentioned before, I wouldn't recommend using it, but it works. I learned a lot in the course of finding a solution which I now consider the main benefit.

Community
  • 1
  • 1
stackmik
  • 151
  • 1
  • 10