Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Wie erzeugt man ein Event? (https://www.delphipraxis.net/167798-wie-erzeugt-man-ein-event.html)

UliBru 19. Apr 2012 10:04

Wie erzeugt man ein Event?
 
Ich habe in einem früheren Thread, siehe hier mal gefragt wie ich ein Datenereignis festellen kann.
Mittlerweile habe ich daraus folgendes gemacht:
In einem eigenen Thread bearbeite ich zyklisch eintreffende Daten und speichere diese in zwei wechselseitigen Puffern. Grundidee: während der eine beschrieben wird, kann der zweite verarbeitet werden. Das klappt soweit.
Nun würde ich gerne im VCL-Thread eine Ereignisprozedur verwenden, die genau dann aufgerufen wird, wenn ein Pufferwechsel stattgefunden hat, natürlich mit dem Parameter, welcher der beiden Puffer nun fürs Lesen freigegeben ist. Quasi als würde der Thread einen Button-Click auslösen, mit dem dann in der zugehörigen onClick-Methode der Lesepuffer verarbeitet wird.

Meine Fragen: wie erzeuge ich dieses Event aus dem Thread heraus? Wie sieht eine Grundstruktur hierzu aus, was muss in der Main-Unit stehen und was im Thread? Gibt es da ein einfaches verständliches Beispiel?

Uli

Luckie 19. Apr 2012 10:10

AW: Wie erzeugt man ein Event?
 
http://www.michael-puff.de/Programmi...reignis1.shtml

Aber man kann auch mit Nachrichten arbeiten. Ist eventuell einfacher.

DeddyH 19. Apr 2012 10:13

AW: Wie erzeugt man ein Event?
 
Deklaration eines EventHandlers:
Delphi-Quellcode:
type
  TMyEvent = procedure(<Parameter>) of object;

  TDings = class
  private
    FOnEvent: TMyEvent;
    ...
  public
    property OnEvent: TMyEvent read FOnEvent write FOnEvent;
    ...
  end;

implementation

procedure TDings.DoSomething;
begin
  DoWork;
  if Assigned(FOnEvent) then
    FOnEvent(<Parameter>);
end;
Wobei man bei Events eines Threads (meistens) darauf achten sollte, dass man diese synchronisiert auslöst.
Delphi-Quellcode:
procedure TMyThread.Execute;
begin
  ...
  Synchronize(DoEvent);
  ...
end;

procedure TMyThread.DoEvent;
begin
  if Assigned(FOnEvent) then
    FOnEvent(<Parameter>);
end;

UliBru 19. Apr 2012 11:25

AW: Wie erzeugt man ein Event?
 
Danke. Hab es nun wie folgt programmiert.

Im der Main-Unit:
Delphi-Quellcode:
interface

  type
  TMainForm = class(TForm)
  private
    ...
  public
    ...
    procedure OnBufferSwitch(bufidx:integer);
  end;

implementation

procedure TMainForm.OnBufferSwitch(bufidx:integer);
begin
  Label1.Caption := IntToStr(bufidx);
  DoSomething(bufidx);
end;
und dann in der Thread-Unit:
Delphi-Quellcode:
interface

type
  TOnBufferSwitch = procedure(bufidx: Integer) of object;

type
  TmyThread = class(TThread)
  private
    ...
    bufidx: integer;
    FOnEvent: TOnBufferSwitch;
  protected
    procedure Execute; override;
  public
    property OnEvent: TOnBufferSwitch read FOnEvent write FOnEvent;
  end;

implementation

procedure TVASThread.Execute;
var oldbufidx: integer;
begin
  FOnEvent := MainForm.OnBufferSwitch; //Zuweisung Ereignisprozedur
  while not(Terminated) do
  begin
    Fillbuffers; //do something
    ...
    if bufidx <> oldbufidx then
      Synchronize(SyncBufferSwitch);
    sleep(10);
  end;
end;

procedure TmyThread.SyncBufferSwitch;
begin
  if Assigned(FOnEvent) then
    FOnEvent((bufidx);
end;
Scheint zu klappen. :-D

TBx 19. Apr 2012 11:29

AW: Wie erzeugt man ein Event?
 
Schmeiß mal ganz schnell die Zuweisung des Eventhandlers aus der Execute-Methode.
Diese Zuweisung gehört dahin, wo auch das Threadobjekt created wird (ich vermute mal. Deine Main-Unit).
Ansonsten müßte ja der Thread Dein Hauptformular kennen und das widerspricht absolut der OOP.

Gruß aus dem Hohen Norden

Thom 19. Apr 2012 11:54

AW: Wie erzeugt man ein Event?
 
Zitat:

Zitat von TBx (Beitrag 1162653)
Ansonsten müßte ja der Thread Dein Hauptformular kennen und das widerspricht absolut der OOP.

:gruebel:

UliBru 19. Apr 2012 12:02

AW: Wie erzeugt man ein Event?
 
Zitat:

Zitat von TBx (Beitrag 1162653)
Schmeiß mal ganz schnell die Zuweisung des Eventhandlers aus der Execute-Methode.
Diese Zuweisung gehört dahin, wo auch das Threadobjekt created wird (ich vermute mal. Deine Main-Unit).
Ansonsten müßte ja der Thread Dein Hauptformular kennen und das widerspricht absolut der OOP.

Danke, erledigt. Hatte das Threadobject direkt mit non-suspended kreiert, daher die (falsche) Zuweisung in Execute. Man lernt eben nie aus.

Medium 19. Apr 2012 12:42

AW: Wie erzeugt man ein Event?
 
So konterkarierst du allerdings die Nebenläufigkeit, da der Thread für die Dauer die der Handler braucht ja steht, und der Buffer-Flip nicht mehr nötig wäre. Sauberer und mehr in deinem Sinne wären Fensternachrichten, die asynchron via PostMessage() möglich sind. Gerüst:

Delphi-Quellcode:
unit uDataThread;
const
  WM_BUFFERFLIP = WM_USER + 1234;

type
  TDataThread = class(TThread)
  private
    FHandlerHanlde: HWND;
    Buffer1, Buffer2, CurrentBuffer: TMyBufferType;
    Buffer1CS, Buffer2CS, CurrentCS: TCriticalSection;
  protected
    procedure Execute; override;
  public
    constructor Create(aHandlerHandle: HWND);
  end;

implementation

constructor TDataThread.Create(aHandlerHandle: HWND);
begin
  inherited Create(false);
  FHandlerHandle := aHandlerHandle;
end;

procedure TDataThread.Execute;
begin
  repeat
    CurrentCS.Enter;
    try
      // Do things with CurrentBuffer
    finally
      CurrentCS.Leave;
    end;

    if DoBufferFlip then
    begin
      PostMessage(FHandlerHandle, WM_BUFFERFLIP, Integer(CurrentBuffer), Integer(CurrentCS));
      FlipBuffersAndCriticalSections;
    end;
  until Terminated;
end;

{-------------------------------------------}
unit uMainForm;

uses
  uDataThread;

type
  TMainForm = class(TForm)
  private
    FDataThread: TDataThread;
    procedure BufferHandler(var Msg: TMessage); message WM_BUFFERFLIP;
  public
  end;

implementation

procedure TMainForm.BufferHandler(var Msg: TMessage);
begin
  TCriticalSection(Msg.WParam).Enter;
  try
    DoSomethingWithBuffer(TMyBufferType(Msg.LParam));
  finally
    TCriticalSection(Msg.WParam).Leave;
  end;
end;
Die CriticalSections sind dann wichtig, wenn der Handler länger braucht als das Befüllen eines Buffers, gehören aber auch zum "guten Ton". Ich persönlich würde sogar noch einen Schritt weiter gehen, und eine Liste von Buffern machen. Fertige werden vom Thread an diese gehängt und das MainForm via PostMessage() darüber informiert. Der Handler schnappt sich dann immer den ältesten Buffer, tut was er damit tun soll, und kümmert sich um die Freigabe sowie Löschung aus der Liste. Idealerweise schaut der dann grob so aus:
Delphi-Quellcode:
  while DataThread.BufferList.Count>0 do
  begin
    DoStuffWithBuffer(DataThread.BufferList[0]); // muss nicht synchronisiert werden, da der Thread diesen Buffer nicht mehr anfasst, und in der Liste nur hinten Added
    DataThread.ListCS.Enter;
    try
      DataThread.BufferList.Delete(0); // sollte hingegn doch gesynced werden, weil dabei Daten in der Liste verschoben werden, was sich bei einem parallelen Add() im Thread schlecht macht (TList verwendet intern Arrays, es ist keine einfache Linked-List.)
    finally
      DataThread.ListCS.Leave;
    end;
  end;
Hier muss also nur noch das Adden und Deleten in der Liste duch eine CS gesichert werden, und man könnte im Thread auch mehrere Buffer in die Liste werfen bevor man die Verarbeitung im MainForm anstößt. Zudem hat man so eine handliche Queue, die Phasen großem Buffer-Aufkommens flexibel puffert, und der Handler kann nach und nach alles abarbeiten wenn Zeit dafür ist.

UliBru 19. Apr 2012 13:38

AW: Wie erzeugt man ein Event?
 
Danke, :thumb: das sieht nicht schlecht aus. Muss ich aber erst einmal verdauen (=verstehen, realisieren, testen). Ich schau mir das mal näher an.
Vermutlich komme ich aber ohne TList aus, die Daten müssen später auch wieder zeitgerecht ausgegeben werden, da kann ich mir ein Buffer-Overrun oder -Underrun sowieso nicht leisten, dann muss das Programm stehen bleiben. Ergo muss der Handler schnell genug arbeiten. Und ich kann dann noch mit der Puffergröße angleichen.

silver-moon-2000 19. Apr 2012 14:59

AW: Wie erzeugt man ein Event?
 
Tut mir leid, wenn ich diesen Thread einfach so entere und nichts sinnvolles beisteuern kann, aber ich hätte mal dazu eine Frage:

Sowohl Luckie als auch Medium raten hier zu Messages anstelle von Events. Kann mir einer der Materie-Kundigen kurz darlegen, warum hier Messages einfacher / vorzuziehen sind?
Oder etwas allgemeiner: Was ist der Unterschied zwischen Events und Messages (mit Ausnahme der möglichen Asynchronität (mithilfe von PostMessage (im Gegensatz zu SendMessage)))?

Oder, nochmal umformuliert: Gibt es Guidelines, wann welches Konstrukt sinnvoller / besser / effektiver / wasauchimmerer einzusetzen ist, oder ist das (wie so vieles) eher dem Gusto des Programmierers überlassen?

Medium 19. Apr 2012 15:31

AW: Wie erzeugt man ein Event?
 
Der für mich oftmals entscheidende Vorteil von Messages ist, dass sie eine sehr lose Kopplung darstellen, und "Multicasting" ohne weiteres Zutun geht, da ohnehin an alle Top-Level Fenster verschickt wird. Da liegt aber auch einer der Nachteile: Wenn die verarbeitende Einheit nicht sinnvoll in "Fensternähe" passieren kann, muss man sich manuell ein Handle besorgen und die WndProc bauen.
Sobald Threads im Spiel sind, nehme ich die fast immer zur Kommunikation mit "aussen", vor allem weil man sonst auch gerne mal so Finten bekommt, dass man eine SQL-Connection eher unbemerkt in einem anderen Threadkontext benutzt als sie erstellt wurde (was meist schief geht). Andere Systemresourcen sind da ähnlich empfindlich, und durch die Messages ist der Kontext eindeutig.

Wenn es nicht zu erwarten ist, dass ein Objekt mal Threadkontextübergreifend "quasseln" muss, greife ich aber genau so gerne zu den üblichen Eventhandlern (aka Methodenzeigern). Deren Vorteil liegt am ehesten noch im Übergeben von mehreren Parametern und ohne Rumgecaste. Letztlich ist das Argument "komfortabel bei Asynchronität" das schwerwiegendste bei Messages.

Sir Rufo 19. Apr 2012 21:34

AW: Wie erzeugt man ein Event?
 
Da der TE mit Delphi XE2 arbeitet bietet sich natürlich auch Delphi-Referenz durchsuchenTThread.Queue an als nicht-blockierende Alternative zu Synchronize.
Und der Uwe hat dazu auch einen schönen Artikel auf seiner Seite veröffentlicht, wie man sich sehr elegant die Zwischenvariable schenken kann.

UliBru 20. Apr 2012 09:23

AW: Wie erzeugt man ein Event?
 
Lauter interessante Vorschläge. Ich verwende nun Queue als vielleicht einfachste Lösung. Klar ist mir, dass die Verarbeitung im Main schnell genug sein muss bevor der nächste asynchrone Aufruf kommt. Dafür werkelt eben der Thread ungebremst vor sich hin.

Ich stell mal meine Lösung hier rein. Falls da noch etwas falsch sein sollte wäre ich für Hinweise dankbar. Ansonsten steht es als Muster zur Verfügung.

Im der Main-Unit:
Delphi-Quellcode:
interface

  type
  TMainForm = class(TForm)
  private
    ...
  public
    ...
    myThread: TmyThread;
    buffer: TmyBuffer;
    bufsize: integer;
    procedure OnBufferSwitch(buffer: tmyBuffer; bufidx: Integer);
  end;

implementation

procedure TMainForm.FormCreate(Sender: TObject);
begin
  bufsize := ...;
  buffer := TmyBuffer.Create(bufsize);
  myThread := TmyThread.Create(true);  //erst suspended
  myThread.Buffer := buffer;           //Buffer übergeben
  myThread.FOnEvent := OnBufferSwitch; //Zuweisung Ereignisprozedur nun hier im Main
  myThread.FreeOnTerminate := true;
  myThread.Start;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  myThread.Terminate;
  buffer.Free;
end;

procedure TMainForm.OnBufferSwitch(bufidx: Integer);
begin
  DoSomething(buffer,bufidx); //Auswertung Datenpuffer
end;
und dann in der Thread-Unit:
Delphi-Quellcode:
interface

type
  TOnBufferSwitch = procedure(bufidx: Integer) of object;

type
  TmyThread = class(TThread)
  private
    fbuffer: tmyBuffer;
    foldbufidx: integer;
    fbufidx: integer;
    FCS: TCriticalSection;
    procedure AsyncBufferSwitch;
  protected
    procedure Execute; override;
  public
    FOnEvent: TOnBufferSwitch;
    property Buffer: tmyBuffer read fbuffer write fbuffer;
    property OnEvent: TOnBufferSwitch read FOnEvent write FOnEvent;
    constructor Create(suspended:boolean); reintroduce;
    destructor Destroy; override;
  end;

implementation

constructor TmyThread.Create(suspended:boolean);
begin
  inherited Create(suspended);
  FCS := TCriticalSection.Create;
end;

destructor TmyThread.Destroy;
begin
  FCS.Free;
  inherited;
end;

procedure TmyThread.Execute;
var oldbufidx: integer;
begin
  FOnEvent := MainForm.OnBufferSwitch; //Zuweisung Ereignisprozedur
  while not(Terminated) do
  begin
    CatchDriverData; //do something
    ...
    FCS.Enter;
    try
      foldbufidx := fbuffer.writebufidx; //aktueller Puffer
      fbuffer.Write(DriverDataArray, count); //inkl. autom. Umschaltung Pufferindex, wenn ein Puffer voll
      fbufidx := fbuffer.writebufidx;    //neuer Pufferindex, falls umgeschaltet
    finally
      FCS.Leave;
    end;
    if bufidx <> oldbufidx then
      Queue(AsyncBufferSwitch);  // <------ hier nun Queue anstelle von Synchronize
    sleep(10);
  end;
end;

procedure TmyThread.AsyncBufferSwitch;
begin
  if Assigned(FOnEvent) then
    FOnEvent((fbufidx+1) and $1);
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:24 Uhr.

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