![]() |
Wie erzeugt man ein Event?
Ich habe in einem früheren Thread, siehe
![]() 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 |
AW: Wie erzeugt man ein Event?
![]() Aber man kann auch mit Nachrichten arbeiten. Ist eventuell einfacher. |
AW: Wie erzeugt man ein Event?
Deklaration eines EventHandlers:
Delphi-Quellcode:
Wobei man bei Events eines Threads (meistens) darauf achten sollte, dass man diese synchronisiert auslöst.
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;
Delphi-Quellcode:
procedure TMyThread.Execute;
begin ... Synchronize(DoEvent); ... end; procedure TMyThread.DoEvent; begin if Assigned(FOnEvent) then FOnEvent(<Parameter>); end; |
AW: Wie erzeugt man ein Event?
Danke. Hab es nun wie folgt programmiert.
Im der Main-Unit:
Delphi-Quellcode:
und dann in der Thread-Unit:
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;
Delphi-Quellcode:
Scheint zu klappen. :-D
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; |
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 |
AW: Wie erzeugt man ein Event?
Zitat:
|
AW: Wie erzeugt man ein Event?
Zitat:
|
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:
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:
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;
Delphi-Quellcode:
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.
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; |
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. |
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? |
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. |
AW: Wie erzeugt man ein Event?
Da der TE mit Delphi XE2 arbeitet bietet sich natürlich auch
![]() Und der Uwe hat dazu auch einen schönen ![]() |
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:
und dann in der Thread-Unit:
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;
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