![]() |
Eventhandler für asynchron arbeitendende Objekte
Verständnisfrage:
Ich habe eine Wrapper-Klasse für ein COM Object gebaut, das asynchron arbeitet. Vereinfacht sieht die Klasse so aus:
Delphi-Quellcode:
Ich habe in einem Projekt 120 Instanzen dieses Callhandlers in einer abgeleiteten ObjectList zusammengefasst.
Type
TCallhandler = class; TCallhandlerStateChangeEvent = procedure(Sender:TCallhandler; aCallstate:APICallstateEnum); TCallhandler = class(TComponent) private FCallhandlerStateChangeEvent: TCallhandlerStateChangeEvent; FCurrentAPICallstate:APICallstateEnum; FAPICallEvents :TAPICallEvents FAPICallObj:IDivaCall; << das COM Object aus einer API procedure OnAPICallstateChange(aCallstate:APICallstateEnum); // wird über ein Sink Object des COM Objectes asynchron ausgelöst public constructor ... destructor ... property Callstate:APICallstate read FCurrentCallstate; property OnCallhandlerStateChange:TCallhandlerStateChangeEvent read FCallhandlerStateChangeEvent write FCallhandlerStateChangeEvent; end; { TCallhandler } procedure TCallhandler.OnCallhandlerStateChange(aCallstate:APICallstateEnum); begin FCurrentAPICallstate := aCallstate; if assigned(FCallhandlerStateChangeEvent) then FCallhandlerStateChangeEvent(self,FCurrentAPICallstate); end;
Delphi-Quellcode:
Type
TCallhandlerGroup = class(TObjectList) private FSumIdle:integer; FSumConnecting:integer; FSumConnected:integer; procedure OnCallhandlerChange(aCallhandler:TCallhandler; aCallstate:APICallstateEnum); procedure UpdateCounters(Sender:TObject); ... public procedure GetUsageCounters(var callhandlerCount,idle,connecting,connected:intger); ... end; { TCallhandlerGroup } procedure TCallhandlerGroup.AddCallhandler(Sender:TObject); var tmpCallhandler:TCallhandler; begin tmpCallhandler:=TCallhandler.create(self); ... tmpCallhandler.OnCallhandlerStateChange := OnCallhandlerChange; ... end; procedure TCallhandlerGroup.OnCallhandlerChange(aCallhandler:TCallhandler; aCallstate:APICallstateEnum); begin ... UpdateCounters; ... end; procedure TCallhandlerGroup.UpdateCounters(Sender:TObject); var i: integer; begin FSumIdle:=0; FSumConnecting:=0; FSumConnected:=0; for i:= Items.count -1 downto 0 do begin with TCallhandler(Items[i]) do begin case Callstate of csIdle: inc(FSumIdle); csConnecting: inc(FSumConnecting); csConnected: inc(FSumConnected); end; end; end; end; procedure TCallhandlerGroup.GetUsageCounters(var callhandlerCount,idle,connecting,connected:intger); begin callhandlerCount := count; // Items.count der ObjectList idle := FSumIdle connecting := FSumConnecting; connected := FSumConnected; end; Selbst auf die Gefahr hin, das ich mich oben bein skizzieren der Klassen vertippt habe. Das System läuft mit 2 Callhandlern stabil. Leider habe ich zuhause keine Möglichkeit das System mit 120 Kanälen (Callhandlern) zu testen. Wenn die Software beim Kunden installiert wird sollte Sie natürlich nicht gleich abrauchen :mrgreen: Alle Instanzen laufen im Haup-Thread der Applikation. Daher hier ein paar Fragen zur Sicherheit: Kann der System Probleme bekommen : a) wenn "UpdateCounters" den Callstate des Callhandler zählt während des Eventhandler "TCallhandler.OnAPICallstateChange" (asynchron ausgelöst vom COM Object) den Status des Callhandlers setzt? (also ggf. Exception weil Leseversuch während Schreibzugriff ?) b) das die Ergebnisse "GetUsageCounters" nicht immer stimmen, weil ein "TCallhandler.OnAPICallstateChange" den Callstate eines Callhandlers ändert, währens UpdateCounters die Nutzung ermittelt ? c) wenn ich hypothetisch die monentane Auslastung für Statistikzwecke in einen TDataset schreiben möchte ... Ist es z.B. möglich, dass GetUsageCounters 2x zur selben Zeit ausgeführt wird, weil 2 Callhandler gleichzeitig ihre Statusänderung über den Eventhandler "FCallhandlerStateChangeEvent" publizieren wollen? Falls ja würde ich Probleme erwarten, wenn ein Callhandler grade den TDataset in den Edit-Modus setzen will, während ein anderer noch in Bearbeitung hat. Wenn es keine Möglichkeit gibt, das ganze zu sauber zu "entkoppeln", dann richte ich lieber Pro Callhandler einen Thread ein, und synchronisiere die Sachen über TCriticalSection bzw. Synchronize ... :hi: Schöne Grüße, Jens |
Re: Eventhandler für asynchron arbeitendende Objekte
Da ich nur einen Thread - den Haupt-Thread der Applikation - habe und die Eventhandler durch die API des Herstellers synchronisiert aufgerufen werden, gehe ich davon aus, das immer ein Event zur Zeit ausgelöst wird, dass anschließend die Ereignisbehandlingsroutine aufgerufen und incl. aller darin aufgerufenen Subroutinen abgearbeitet wird. Anschleißend feuert das nächste, ggf. anstehende, Event der API. Somit sind also keine Parallelitätsprobleme zu erwarten.
Leider bin ich mir nicht sicher, ob es wirklich so einfach ist .... Daher stelle ich meine Frage noch einmal nach oben. Schöne Grüße, Jens :hi: |
Re: Eventhandler für asynchron arbeitendende Objekte
Hi,
all die Probleme die Du nennst sind im Prinzip immer möglich. Um sie zu vermeiden muss eben synchronisiert bzw. gesperrt werden. Das Lesen bzw. Schreiben eines primitiven Typen wird i.d.R. als atomar angesehen (stellt sich immer die Frage was primitiv hier bedeutet), der ganze Rest kann immer Überschneidungen mit sich bringen. Wenn Du auf eine fremde Komponente zugreifst, so ist es einfach wichtig zu wissen, ob diese Threadsafe ist oder nicht. Ist dies nicht der Fall, musst Du Dich selbst um die Synchronisation kümmern, mit allen Freiheiten und Gefahren. Dabei basiert gute Synchronisation immer auf dem Prinzip so spät wie möglich, so lang wie nötig (also bei Sperrsynchronisation). Man sperrt den Zugriff auf Ressourcen also immer erst, wenn es wirklich nötig wird und gibt die Sperren möglichst schnell wieder frei, hält sie aber auch wirklich bis sicher ist, dass es nicht zu einer Verklemmung kommen kann. Zitat:
Jedenfalls musst Du hier wirklich schauen, was genau die Komponente für Dich bereits übernimmt. Alles was Du selbst implementierst ist erstmal nicht threadsafe (außer die Komponente sichert das irgendwo zu oder Du sorgst selbst dafür). Davon auszugehen, dass die Komponente immer ein Event komplett abarbeitet bevor das nächste bearbeitet wird solltest Du auf keinen Fall. Selbst wenn das auf verschiedenen Systemen 999.999 mal der Fall ist, beim nächsten mal muss das nicht der Fall sein. Hier kann man sich nie auf Beobachtung verlassen, man muss es sicherstellen (z.B. durch eine Zusicherung der Doku oder eben durch ein eigenes Verfahren). Wie gesagt, möglich/denkbar sind viele Probleme, kommt drauf an was die Komponente schon leistet (oder eben nicht). Selbst synchronisieren solltest Du nur wenn Du musst (kostet Perfomance und ist fehleranfällig). Ist die schon synchronisiert, solltest Du auf keinen Fall noch zusätzliche Sperren einführen (der Overhead ist schnell größer als jeglicher Gewinn durch die parallele Abarbeitung). Gruß Der Unwissende |
Re: Eventhandler für asynchron arbeitendende Objekte
Ich habe nun eine Antwort von dem API Lieferanten.
Alle Eventhandler und Callbacks werden dort synchronisiert aufgerufen. Die API ist also threadsicher. Bleibt nur noch die Frage wie Delphi arbeitet ... IMHO können alle Events nur rein seriell abgearbeitet werden, da alle Events im HauptThread meiner Applikation behandelt werden. Das Ablaufschema sollte also immer so aussehen:
Delphi-Quellcode:
und nie so:
> OnIrgendEinEvent Start
>> EineSubProcedure 1 >> EineSubProcedure 2 >> EineSubProcedure 3 > OnIrgendEinEvent Ende > OnIrgendEinEvent Start >> EineSubProcedure 1 >> EineSubProcedure 2 >> EineSubProcedure 3 > OnIrgendEinEvent Ende > OnIrgendEinEvent Start >> EineSubProcedure 1 >> EineSubProcedure 2 >> EineSubProcedure 3 > OnIrgendEinEvent Ende
Delphi-Quellcode:
Fakt ist, dass ich die Eventbehandlungen so schlank wie möglich halten muss, damit ich mich auf den Status der Callhandler verlassen kann. Für länger laufende Prozeduren werde ich besser an Alzaimars Workerthread bemühen ...
> OnIrgendEinEvent Start
>> EineSubProcedure 1 >> EineSubProcedure 2 > OnIrgendEinEvent Start >> EineSubProcedure 3 >> EineSubProcedure 1 >> EineSubProcedure 2 > OnIrgendEinEvent Ende >> EineSubProcedure 2 >> EineSubProcedure 3 > OnIrgendEinEvent Ende Danke. Schöne Grüße, Jens :hi: |
Re: Eventhandler für asynchron arbeitendende Objekte
Zitat:
Aktivierst Du nun die endlosschleife, so solltest Du theoretisch immer noch auf den 2ten Button klicken können und das wiederum sollte seine Ereignisbehandlung auslösen. An sich ist hier nicht die Frage, aus welchem Thread heraus die Ereignisbehandlung gestartet wird, wichtig ist in welchem Thread die Ereignisbehandlung abläuft. Sobald diese in einem eigenen Thread läuft (kann z.B. auch durch eine verwendete Komponente der Fall sein) wird auch eine nicht-sequentielle Ausführung möglich. |
Re: Eventhandler für asynchron arbeitendende Objekte
Hi,
natürlich stimmt das so. Die Unterbrechung der seriellen Abarbeitung kommt aussschliesslich durch das Application.ProcessMessages zustande. Wenn er das nicht in seine serielle Abarbeitung einbaut, hat er genau das was er will. Gruss |
Re: Eventhandler für asynchron arbeitendende Objekte
Zitat:
|
Re: Eventhandler für asynchron arbeitendende Objekte
Ich habe nun alle Prozeduren, in denen eine serielle Bearbeitung nötig ist, durch eine CriticalSection geschützt.
Wenn die Eventhandler von Haus aus seriell aufgerufen werden, spielt es zeitlich fast keine Rolle, ob die Prozeduren durch die CS laufen. Wenn Events zufällig absolut zeitgleich ausgelöst werden, bringt die CS hoffentlich den notwendigen Schutz. Laut dem Debug-Protokoll meiner Applikation wird die CS auf meinem Rechner innerhalb 1-2 Millisekunden durchlaufen (bei 2 Callhandlern). Beim Kunden (mit 120 Callhandlern) sollte der kritische Abschnitt also nach max. 120ms durchlaufen sein. Das ist für mich OK. Die CS sorgt ja dafür, dass der geschützte Code-Abschnitt nur von einem Thread zur Zeit durchlaufen werden kann. Alle Eventhamdler werden im HauptThread meiner Applikation ausgeführt. Daher stellt sich also noch die Frage, ob die CS auch wirksam wird. Schöne Grüße, Jens :hi: |
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:58 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