Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Aufrufen einer Unterprozedur mit AsyncCalls (https://www.delphipraxis.net/189139-aufrufen-einer-unterprozedur-mit-asynccalls.html)

Benmik 8. Mai 2016 16:46

Aufrufen einer Unterprozedur mit AsyncCalls
 
Ich habe eine Prozedur mit Unterprozeduren:
Delphi-Quellcode:
procedure TForm1.Haupt;
var i:integer;
  //-------------------------------------------------------
  function TuWas(i:integer):integer;
  begin
  end;
  //-------------------------------------------------------
  procedure RufeAuf;
  begin
    TAsyncCalls.Invoke(
      procedure
      var i:integer;
      begin
        For i := 0 to 10 do
          If i < 11
            then TAsyncCalls.Invoke<integer>(TuWas,i));
    end);
  end;
  //-------------------------------------------------------
begin
  RufeAuf;
end;
So, wie es da oben steht, funktioniert das natürlich nicht. In Wirklichkeit ist TuWas bei mir eine Klassenmethode von TForm1, und das funktioniert ohne Probleme. Ich hätte es aber aus rein codeökonomischen Gründen gern in einer Unterfunktion. Ich habe auch eine Ahnung, wie man das machen könnte, insbesondere von hier. Ich kriege es aber nicht hin, insbesondere nicht mit AsyncCalls. Ich bin mir aber sicher, dass das einige von euch nur ein müdes Lächeln kostet.

Sir Rufo 8. Mai 2016 17:18

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Was erwartet Invoke denn als Argument? Daran kann man sehen was geht und was nicht

Benmik 8. Mai 2016 17:26

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Delphi-Quellcode:
class function Invoke<T>(Event: TAsyncCallArgGenericMethod<T>; const Arg: T): IAsyncCall; overload; static;

  IAsyncCall = interface
    { Sync() waits until the asynchronous call has finished and returns the
      result value of the called function if that exists. }
    function Sync: Integer;

    { Finished() returns True if the asynchronous call has finished. }
    function Finished: Boolean;

    { ReturnValue() returns the result of the asynchronous call. It raises an
      exception if called before the function has finished. }
    function ReturnValue: Integer;

    { ForceDifferentThread() tells AsyncCalls that the assigned function must
      not be executed in the current thread. }
    procedure ForceDifferentThread;

    { added by Zarko Gajic to support canceling tasks waiting in the ThreadPool queue}
    procedure Cancel;
  end;

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

Sir Rufo 8. Mai 2016 17:29

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Jo, dann gehen da nur Methoden rein - ob der Typ wohl deswegen
Delphi-Quellcode:
TAsyncCallArgGenericMethod
(bemerke Method) heißt? :stupid:

Wenn du das ändern möchtest dann einfach
Delphi-Quellcode:
// TAsyncCallArgGenericMethod<T> = function(Arg: T): Integer of object;
TAsyncCallArgGenericMethod<T> = reference to function(Arg: T): Integer;
dann geht da alles rein.

(Den Namen muss man nicht ändern, sollte man aber)

Uwe Raabe 8. Mai 2016 17:34

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Da
Delphi-Quellcode:
Invoke
als overload deklariert ist, gibt es doch sicher noch andere Möglichkeiten, oder?

Z.B. diese
Delphi-Quellcode:
   
      TAsyncCallArgGenericProc<T> = function(Arg: T): Integer;

class function Invoke<T>(Proc: TAsyncCallArgGenericProc<T>; const Arg: T): IAsyncCall; overload; static;

Das wäre dann die mit der globalen Function, wobei ich dann aber die Object Method vorziehen würde.

Leider gibt es aber keine mit
Delphi-Quellcode:
reference to
, weswegen der Ansatz aus dem SO Link nicht zieht.

Sir Rufo 8. Mai 2016 17:35

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Dann kann man die ja als overload noch hinzufügen :wink:

Benmik 8. Mai 2016 17:39

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Da wollte ich auf Sir Rufo antworten:
"Sind anonyme Methoden nicht auch Methoden? Ich erinnere mich an Lösungen á la
Delphi-Quellcode:
M:TMethod
."

... aber da waren mir die Ereignisse schon voraus. Also OK, ich haue
Delphi-Quellcode:
TAsyncCallArgGenericMethod<T> = reference to function(Arg: T): Integer;
rein, und dann???

Sir Rufo 8. Mai 2016 17:41

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Dann kannst du Methoden, Proceduren und anonyme Methoden übergeben.
Delphi-Quellcode:
reference to
deckt alle drei Fälle ab.
Delphi-Quellcode:
TFooMethod = procedure of object; // NUR Methoden
TFooProc = procedure; // NUR Prozeduren
TFooAnon= reference to procedure; // Methoden, Prozeduren, Anonyme Methoden
Jetzt klar?

Uwe Raabe 8. Mai 2016 17:43

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Zitat:

Zitat von Sir Rufo (Beitrag 1337751)
Dann kann man die ja als overload noch hinzufügen :wink:

Da die Entwicklung von AsyncCalls aktuell eingestellt ist, wäre das ein sinnvoller Ansatz. Ich fürchte nur, daß für die Implementierung noch ein bisschen mehr Boilerplate-Code erforderlich sein wird.

Sir Rufo 8. Mai 2016 17:46

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1337754)
Zitat:

Zitat von Sir Rufo (Beitrag 1337751)
Dann kann man die ja als overload noch hinzufügen :wink:

Da die Entwicklung von AsyncCalls aktuell eingestellt ist, wäre das ein sinnvoller Ansatz. Ich fürchte nur, daß für die Implementierung noch ein bisschen mehr Boilerplate-Code erforderlich sein wird.

Wenn die Anonymen dort noch gar nicht verwendet werden, dann wird es eher weniger :stupid:

Benmik 8. Mai 2016 17:55

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Zitat:

Zitat von Sir Rufo (Beitrag 1337753)
Jetzt klar?

Leider nein...:(
Ich habe mal Folgendes probiert:
Delphi-Quellcode:
procedure TForm1.Haupt;
type
  TTuWas = reference to function(i:integer):integer;
var TuWas:TTuWas ;
  //-------------------------------------------------------
  procedure RufeAuf;
  begin
    TAsyncCalls.Invoke(
      procedure
      var i:integer;
      begin
        For i := 0 to 10 do
          If i < 11
            then TAsyncCalls.Invoke<integer>(TuWas,i));
    end);
  end;
  //-------------------------------------------------------
begin
  RufeAuf;
end;
Der Compiler meint "Es gibt keine überladene Version usw....")
Abgesehen davon, dass TuWas ja noch fehlt, schreit er jetzt schon. Das ist es ja, wie geht sowas?

Sir Rufo 8. Mai 2016 17:58

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Weil der Typ nicht passt.
Delphi-Quellcode:
var
  TuWas: TAsyncCallArgGenericMethod<Integer>;

Benmik 8. Mai 2016 18:12

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Liste der Anhänge anzeigen (Anzahl: 1)
Oh je, wenn mal Uwe nicht Recht behält...
Sir Rufos Vorschlag wird auch nicht gnädig behandelt, da die Deklaration private ist...
Ich hänge die Datei mal an.

Sir Rufo 8. Mai 2016 21:35

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Wenn man keine Rücksicht auf Abwärtskompatibilität legen muss, dann kann man alles in eine normale anonyme Methode packen:

Delphi-Quellcode:
TAnonWrapper = class
  class function Wrap<T>( AProc: TProc<T>; Arg: T ) : TProc;
  class function Wrap<T,TResult>( AFunc: TFunc<T,TResult>; Arg: T ) : TFunc<TResult>;
  // das kann man jetzt mit beliebig vielen Argumenten weiterspinnen
end;

class function TAnonWrapper.Wrap<T>( AProc: TProc<T>; Arg: T ) : TProc;
begin
  Result :=
    procedure ()
    begin
      AProc( Arg );
    end;
end;

class function TAnonWrapper.Wrap<T,TResult>( AFunc: TFunc<T,TResult>; Arg: T ) : TFunc<TResult>;
begin
  Result :=
    function(): TResult
    begin
      Result := AFunc( Arg );
    end;
end;
Man kann sich also das gesamte Interface-Implementierungs-Boilerplate-Geraffel komplett sparen und alles euf einen gemeinsamen Typen herunterbrechen.

Und wenn ich nur noch eins implementieren muss, dann ist das halt weniger.

Benmik 9. Mai 2016 23:17

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
So leid es mir tut, beim Anblicken des Codes habe ich eine Ahnung, mehr aber auch nicht.
Wie gesagt, ich habe ja funktionierenden Code, ich möchte nur begreifen, wie man das geschilderte Problem löst.
Also ganz schlicht, der Teil
Delphi-Quellcode:
  //-------------------------------------------------------
  procedure RufeAuf;
  begin
    TAsyncCalls.Invoke(
      procedure
      var i:integer;
      begin
        For i := 0 to 10 do
          If i < 11
            then TAsyncCalls.Invoke<integer>(TuWas,i));
    end);
  end;
  //-------------------------------------------------------
sähe dann wie aus?

Da wir schon beim Lernen sind, den Vorschlag
Delphi-Quellcode:
var
  TuWas: TAsyncCallArgGenericMethod<Integer>;
konnte ich nicht umsetzen da es mir nicht gelang, TAsyncCallArgGenericMethod sichtbar zu machen. Ich habe die Deklaration in AsyncCalls public gemacht, das hat den Compiler leider nicht interessiert.
Und wie sähe denn dann die Lösung aus?
Vielen Dank.

Benmik 23. Mai 2016 17:21

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Wie vielleicht der eine oder andere mitbekommen hat, habe ich die Frage noch einmal auf stackoverflow gestellt, da ich einfach nicht weiterkam. Das Ganze verlief mehr als unbefriedigend und endete mit einem Downvote von David Heffernan. Der jetzige Stand dort ist, dass mein Vorhaben nicht möglich ist.

Von der Diskussion hier habe ich zwei Dinge mitgenommen:
  1. Es ist möglich
  2. Aus pädagogischen Gründen liefern wir dir aber nicht die fertige Lösung
Und da bitte ich doch unsere Lords und Dukes hier, ihrem Herzen einen Stoß zu geben und zu zeigen, wie es geht. Weltweiter Dank wäre euch sicher.

Benmik 11. Jun 2016 12:22

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Nachdem ich mich ziemlich in das Problem verbissen habe, möchte ich hier eine Lösung präsentieren. Nochmal zu Erinnerung, hier geht es nicht um die Lösung eines konkreten Problems, da eine solche bereits vorliegt. Hier geht es um das Verstehen von Strukturen.
Ausgangspunkt war der apodiktische Verweis von David Heffernan auf die Dokumentation, die klar aussagt: "Nested procedures and functions (routines declared within other routines) cannot be used as procedural values." Demgegenüber steht die Erfahrung, dass viele Dinge möglich sind, auch wenn die Dokumentation (und David Heffernan) etwas anderes sagen.
Ich hatte einfach folgende (laienhafte) Vorstellung:
  1. Jede Prozedur/Funktion hat eine Adresse
  2. Der Compiler kennt diese Adresse und hat sie irgendwo gespeichert
  3. Findet man diese Adresse, kann man die Prozedur/Funktion ausführen
Wie also kriegt man einen Pointer auf diese Adresse?
Erhellend (und zunächst enttäuschend) war diese Diskussion auf Stackoverflow vor 4 Jahren, wieder mit Beteiligung von DH. Nach dem Verweis auf die Dokumentation führte er aus:"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." Der Threadersteller schaute augenscheinlich im Assemblercode nach und fand: "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'." Alles ein klein wenig außerhalb meiner Reichweite.
Aber dann gibt es noch diesen Text mit dem Titel: "How to pass nested routine as a procedural parameter (32 bit )". Verblüffend, wenn man die Aussage der Dokumentation bedenkt. Dieser Text führt zu folgendem Code (Voraussetzung: Die Änderung von Sir Rufo mit
Delphi-Quellcode:
reference to
):
Delphi-Quellcode:
procedure TForm1.Haupt;
var i:integer;
  //-------------------------------------------------------
  function TuWas(i:integer):integer;
  begin
  end;
  //-------------------------------------------------------
  procedure RufeAuf;
  var p:Pointer;
  begin
    p := @TuWas;
    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
  RufeAuf;
end;
Dieser Code funktioniert, sogar mit der Helper-Klasse von Garco Zajik. Jetzt ist p nicht direkt eine prozedurale Variable, ist aber eigentlich wurscht, die Frage war ja, ob die Klassenmethode in eine Unterprozedur verlagert werden kann. Wie es aussieht, geht das doch. Was sagen die Experten?

dummzeuch 11. Jun 2016 13:48

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Zitat:

Zitat von Benmik (Beitrag 1339938)
die Frage war ja, ob die Klassenmethode in eine Unterprozedur verlagert werden kann. Wie es aussieht, geht das doch. Was sagen die Experten?

Das Problem ist, dass diese Lösung auf Compiler-Interna aufbaut, die nicht dokumentiert sind und ggf. bei der nächsten Version geändert werden können.

Benmik 12. Jun 2016 13:01

AW: Aufrufen einer Unterprozedur mit AsyncCalls
 
Ja, das ist richtig, auch funktioniert es nicht unter 64 Bit. Zudem gibt es noch weitere Einschränkungen, in bestimmten Fällen muss man globale Variablen benutzen (wegen der Stack-Problematik?).
Fazit also: Lohnt sich nicht. Trotzdem gut, dass die liebe Seele jetzt Ruh hat. Und interessant war's auch.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:01 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz