Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Threads und Eventhandler (https://www.delphipraxis.net/200006-threads-und-eventhandler.html)

Codehunter 10. Mär 2019 18:45

Threads und Eventhandler
 
Hallo!

Ich mache gerade meine ersten Schritte in Multithreading und da klemmt es gerade dabei, Eventhandler richtig anzusprechen. Hier ein stark vereinfachtes Testprogramm, dass das Problem verdeutlichen soll. Soweit ich das verstehe hat der Thread trotz EnterCriticalSection keinen Zugriff auf die Eventhandler-Methodenzeiger der Instanz von TmyClass. Nur warum verstehe ich gerade nicht so recht. Da wäre ich für ein paar Tips dankbar.

Um eine Diskussion gleich zu vermeiden: Ich verwende NOCH kein TThread weil das hier auf einem Demoprogramm aufbaut an dem ich mich erstmal orientieren will. Daher bitte keine "Mit TThread wär das nicht passiert"-Diskussion ;-) Wenn ich mal sicherer bin im Umgang mit Threads, dann mache ich auch eigene Konstruktionen in der Art.

Stürmische Grüße
Cody

Delphi-Quellcode:
unit Test;

interface

uses
  System.Classes, Vcl.Controls, Winapi.Windows;

type
  TmyClass = class(TObject)
  private
    FOnFailed: TNotifyEvent;
    FOnStarted: TNotifyEvent;
  protected
    procedure DoFailed;
    procedure DoStarted;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Produce;

    property OnFailed: TNotifyEvent read FOnFailed write FOnFailed;
    property OnStarted: TNotifyEvent read FOnStarted write FOnStarted;
  end;
 
  TForm1 = class(TForm)
   procedure DoOnFailed(Sender: TObject);
   procedure DoOnStarted(Sender: TObject);
   procedure Form1Create(Sender: TObject);
   procedure Form1Destroy(Sender: TObject);
   procedure Button1Click(Sender: TObject);
  end;

var
  FThreadId: DWord;

implementation

uses
  System.SysUtils;

var
  CS: TRTLCriticalSection;
  myClass: TmyClass;
 
{ TmyClass }

constructor TmyClass.Create;
begin
  FStreamHandle := 0;
  FThreadId := 0;
end;

destructor TmyClass.Destroy;
begin
  { .. }
  inherited;
end;

procedure TmyClass.DoFailed;
begin
  if Assigned(FOnFailed) then begin // <-- FOnFailed = NIL **WARUM??**
    FOnFailed(Self);
  end;
end;

procedure TmyClass.DoStarted;
begin
  if Assigned(FOnStarted) then begin // <-- FOnStarted = NIL **WARUM??**
    FOnStarted(Self);
  end;
end;

procedure TmyClass.Produce;
var
  Dummy: Cardinal;
  Func: TThreadFunc;
  Status: Boolean;

  function _ThreadFunc: Integer;
  begin
    Result := 0;
    Status := MachIrgendwas;
    EnterCriticalSection(CS);
    try
      if Status then begin
        DoStarted;
      end else begin
        DoFailed;
      end;
    finally
      LeaveCriticalSection(CS);
    end;
    FThreadId := 0;
  end;

begin
  if FThreadId = 0 then begin
    FThreadId := BeginThread(nil, 0, @_ThreadFunc, Nil, 0, Dummy);
  end;
end;

{ TForm1 }

procedure Form1Create(Sender: TObject);
begin
  myClass := TmyClass.Create;
  myClass.OnFailed := DoOnFailed;
  myClass.OnStarted := DoOnStarted;
end;

procedure Form1Destroy(Sender: TObject);
begin
  FreeAndNil(myClass);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  myClass.Produce;
end;

initialization

  InitializeCriticalSection(CS);

finalization

  DeleteCriticalSection(CS);

end.

Uwe Raabe 10. Mär 2019 22:03

AW: Threads und Eventhandler
 
Deine _ThreadFunc ist eine lokale Funktion und keine TThreadFunc wie von BeginThread erwartet. Die Notwendigkeit des @-Operators um den Compiler zu besänftigen sollten schon ein Warnsignal gewesen sein, daß das vielleicht nicht ganz richtig sein könnte.
Delphi-Quellcode:
type
  TThreadFunc = function(Parameter: Pointer): Integer;

...
function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord;
  ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord;
  var ThreadId: TThreadID): THandle;
Du musst also eine globale Funktion mit dieser Signatur übergeben. In dem Parameter könntest du den Pointer auf die Objekt-Instanz mitgeben, womit du dann auf DoStarted und DoFailed zugreifen könntest (bei geeigneter Sichtbarkeit). Leider bleibt dir der Zugriff auf die lokale Variable Status aber trotzdem verwehrt. Das ist aber auch gut so, da diese ja nach dem Verlassen von Produce auch nicht mehr existiert (und dann läuft dein Thread vermutlich noch).

Ich verstehe aber wirklich nicht, warum du den definitiv schwierigsten Weg wählst, um dich mit Threading zu beschäftigen, anstatt eine wenigstens halbwegs brauchbare fertige Kapselung zu verwenden. Das obige Problem ist eine direkte Folge dieser Herangehensweise. Wollte man das auch rein mit Delphi Bordmitteln erledigen, wären Anonyme Methoden und TThread oder TTask die bessere Wahl. Ist mir auch egal, ob du das nun hören willst oder nicht.

Codehunter 10. Mär 2019 23:26

AW: Threads und Eventhandler
 
Wenn man schon auf die Nase fällt, dann wenigstens mit ordentlich Anlauf :-D

Wie gesagt, das basiert auf einer (für meine Einsteiger-Verhältnisse) relativ komplexen Demo. Diese wurde offensichtlich für C++ gestrickt und dann quasi 1:1 für Delphi angepasst.

Ich habs jetzt auf TThread umgestrickt. Dort habe ich aber ähnliche Probleme. Variablen, die im Interface-Private des TThread-Objektes definiert sind, ändern sich völlig willkürlich. Deklariere ich sie Unit-global im Interface-Teil, dann funktioniert es, obwohl ich KEIN CriticalSection verwende (was ich eigentlich erwartet hätte dass dann umso mehr Probleme auftauchen müssten).

Threadprogrammierung ist schon sehr verwirrend ^^ Ein andermal mehr, morgen ist wieder Arbeit angesagt...

Der schöne Günther 11. Mär 2019 08:08

AW: Threads und Eventhandler
 
Zitat:

Zitat von uwe raabe (Beitrag 1309422)
aus den slides meines vortrags auf den delphi-tagen 2013:

Zitat:

Zitat von danny thorpe
new programmers are drawn to multithreading like moths to flame,
with similar results.


( ͡° ͜ʖ ͡°)

peterbelow 11. Mär 2019 13:10

AW: Threads und Eventhandler
 
Zitat:

Zitat von Codehunter (Beitrag 1427467)
Wenn man schon auf die Nase fällt, dann wenigstens mit ordentlich Anlauf :-D

Wie gesagt, das basiert auf einer (für meine Einsteiger-Verhältnisse) relativ komplexen Demo. Diese wurde offensichtlich für C++ gestrickt und dann quasi 1:1 für Delphi angepasst.

Ich habs jetzt auf TThread umgestrickt. Dort habe ich aber ähnliche Probleme. Variablen, die im Interface-Private des TThread-Objektes definiert sind, ändern sich völlig willkürlich. Deklariere ich sie Unit-global im Interface-Teil, dann funktioniert es, obwohl ich KEIN CriticalSection verwende (was ich eigentlich erwartet hätte dass dann umso mehr Probleme auftauchen müssten).

Threadprogrammierung ist schon sehr verwirrend ^^ Ein andermal mehr, morgen ist wieder Arbeit angesagt...

Man braucht halt einen anderen mindset als für die klassische RAD-"Programmierung". Wenn Du einigermaßen Fit in Englisch bist lies Dir bei Gelegenheit mal das hier durch: http://sklobovsky.nstemp.com/communi...threadmare.htm


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:45 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