TNxHorizon
Klasse ist vollständig Thread-sicherNXHorizon.Instance
ist threadsicher und kann von jedem Thread aus verwendet werden Ereignistyp deklarieren:
Ereignisse werden nach Typinformationen kategorisiert – TypeInfo
. Jede einzelne Ereigniskategorie erfordert einen eigenen Typ.
type
TFoo = class
...
end ;
TOtherFoo = type TFoo;
TIntegerEvent = type Integer;
TStringEvent = type string;
TFooEvent = INxEvent<TFoo>;
TOtherFooEvent = INxEvent<TOtherFoo>;
Veranstaltung abonnieren/abmelden:
Das Abonnieren von Ereignissen kann zu jeder vorhandenen Klasse hinzugefügt werden.
type
TSubscriber = class
protected
// subscriptions
fIntegerSubscription: INxEventSubscription;
fStringSubscription: INxEventSubscription;
// event handlers
procedure OnIntegerEvent ( const aEvent: TIntegerEvent);
procedure OnStringEvent ( const aEvent: TStringEvent);
public
constructor Create;
destructor Destroy; override;
end ;
constructor TSubscriber.Create;
begin
fIntegerSubscription := NxHorizon.Instance.Subscribe<TIntegerEvent>(Async, OnIntegerEvent);
fStringSubscription := NxHorizon.Instance.Subscribe<TStringEvent>(Sync, OnStringEvent);
end ;
destructor TSubscriber.Destroy;
begin
fIntegerSubscription.WaitFor;
fStringSubscription.WaitFor;
NxHorizon.Instance.Unsubscribe(fIntegerSubscription);
NxHorizon.Instance.Unsubscribe(fStringSubscription);
inherited ;
end ;
procedure TSubscriber.OnIntegerEvent ( const aEvent: TIntegerEvent);
begin
Writeln(aEvent);
end ;
procedure TSubscriber.OnStringEvent ( const aEvent: TStringEvent);
begin
Writeln(aEvent);
end ;
Nachrichten senden:
NxHorizon.Instance.Post<TIntegerEvent>( 5 );
NxHorizon.Instance.Send<TStringEvent>( ' abc ' , Async);
oder
var
IntEvent: TIntegerEvent;
StrEvent: TStringEvent;
IntEvent := 5 ;
StrEvent := ' abc ' ;
NxHorizon.Instance.Post(IntEvent);
NxHorizon.Instance.Send(StrEvent, Async);
Event-Handler-Methoden müssen der folgenden Deklaration entsprechen, wobei T
ein beliebiger Typ sein kann. Für die asynchrone Bereitstellung sind Typen mit automatischer Speicherverwaltung oder Werttypen erforderlich. Sie können auch manuell verwaltete, langlebige Objektinstanzen als Ereignisse verwenden. In solchen Fällen müssen Sie jedoch sicherstellen, dass sie nicht zerstört werden, bevor die bereits gesendeten Nachrichten vollständig verarbeitet wurden.
procedure( const aEvent: T) of object ;
Der Typ TNxHorizonDelivery
deklariert vier Zustellungsoptionen:
Sync
– synchron im aktuellen ThreadAsync
– asynchron in einem zufälligen HintergrundthreadMainSync
– synchron zum HauptthreadMainAsync
– asynchron im Hauptthread Sync
und MainSync
sind BLOCKIERENDE Vorgänge und der Event-Handler wird sofort im Kontext des aktuellen Threads ausgeführt oder mit dem Haupt-Thread synchronisiert. Dadurch wird das Versenden anderer Ereignisse über dieselbe Ereignisbusinstanz blockiert, bis der Ereignishandler abgeschlossen ist. Verwenden Sie es nicht (oder nur sparsam für kurze Ausführungen) auf der Standard-Event-Bus-Instanz.
Wenn das Senden von Ereignissen aus dem Kontext des Hauptthreads erfolgt, verwendet die MainAsync
Zustellung TThread.ForceQueue
, um den Ereignishandler asynchron im Kontext des Hauptthreads auszuführen.
Durch das Abonnieren eines Ereignishandlers wird eine neue INxEventSubscription
Instanz erstellt. Sie sollten die zurückgegebene Instanz speichern, um sie später wieder abzubestellen.
Es gibt zwei Methoden zum Abbestellen: Unsubscribe
und UnsubscribeAsync
.
Beide Methoden kündigen das Abonnement und entfernen es aus der im Ereignisbus verwalteten Abonnementsammlung. Diese Sammlung wird innerhalb der Post
und Send
Methoden iteriert. Jegliche Änderungen zu diesem Zeitpunkt sind nicht zulässig und können zu unerwartetem Verhalten führen.
Um eine Änderung der Abonnentensammlung während der Iteration zu vermeiden, sollten Sie UnsubscribeAsync
verwenden, wenn Sie sich von Code abmelden möchten, der in einem synchron gesendeten Ereignishandler ausgeführt wird. Dadurch wird das Abonnement sofort gekündigt, das tatsächliche Entfernen aus der Sammlung wird jedoch verzögert, da es außerhalb ausgeführt wird Versanditeration.
Asynchron gesendete Ereignishandler werden immer außerhalb der Dispatching-Iteration ausgeführt und ermöglichen die Verwendung der Unsubscribe
-Methode. Die Art und Weise, wie die Handler verteilt werden, kann jedoch durch unabhängigen externen Code geändert werden. Wenn Sie die asynchrone Verteilung nicht absolut garantieren können, ist die Verwendung von UnsubscribeAsync
gerechtfertigt.
Unsubscribe
und UnsubscribeAsync
kündigen auch das Abonnement, bevor sie es aus der Abonnementsammlung entfernen. Normalerweise ist es nicht erforderlich, das Abonnement vor der Abmeldung explizit zu kündigen. Wenn Sie jedoch einen bestimmten Grund haben, warum Sie das Abonnement irgendwann vor der Abmeldung kündigen möchten, können Sie die Methode Cancel
aufrufen. Cancel
kann bedenkenlos mehrmals aufgerufen werden. Sobald ein Abonnement gekündigt wurde, kann sein Status nicht mehr wiederhergestellt werden.
Aufgrund des asynchronen Ereignisversands ist es möglich, dass zu dem Zeitpunkt, zu dem Sie ein bestimmtes Abonnement kündigen oder abbestellen, ein bereits ausgelöster Ereignishandler vorhanden ist. Wenn Sie sich von einem Destruktor, Ihrem Abonnentenklassen-Destruktor, abmelden, kann dies dazu führen, dass Sie während des Zerstörungsprozesses oder nachdem sie zerstört wurde, auf die Abonnenteninstanz zugreifen. Um ein solches Szenario zu verhindern, können Sie WaitFor
für das Abonnement aufrufen, wodurch das Abonnement sofort gekündigt und blockiert wird, bis die Ausführung aller ausgelösten Ereignishandler abgeschlossen ist.
Wenn Sie WaitFor
aus dem Kontext des Hauptthreads aufrufen und Ihre Event-Handler über einen längeren Zeitraum ausgeführt werden, führt dies dazu, dass Ihre Anwendung für diesen Zeitraum nicht mehr reagiert.
Die BeginWork
und EndWork
-Methoden sind Teil des Abonnement-Wartemechanismus. Wenn Sie Code in einem Event-Handler in einem anderen Thread ausführen müssen und sicherstellen müssen, dass auch auf den Code gewartet wird, können Sie BeginWork
aufrufen, bevor Sie einen solchen Thread starten, und EndWork
nachdem er beendet ist. Stellen Sie sicher, dass alle Codepfade irgendwann ein passendes EndWork
aufrufen, da dies sonst zu einem Deadlock führt, wenn Sie WaitFor
aufrufen.
procedure TSubscriber.OnLongEvent ( const aEvent: TIntegerEvent);
begin
fIntegerSubscription.BeginWork;
try
TTask.Run(
procedure
begin
try
...
finally
fIntegerSubscription.EndWork;
end ;
end );
except
fIntegerSubscription.EndWork;
raise;
end ;
end ;
procedure Post <T>( const aEvent: T);
procedure Send <T>( const aEvent: T; aDelivery: TNxHorizonDelivery);
Die Post
Methode wird zum Veröffentlichen von Ereignissen verwendet, bei denen die Zustellungsoption von der Abonnement-Zustellungsoption abhängt, die beim Abonnieren des Ereignisses festgelegt wurde.
Die Send
Methode überschreibt die Abonnementzustellungsoption und sendet ein Ereignis auf eine Weise, die durch den übergebenen aDelivery
Parameter bestimmt wird. Wenn das Abonnement das Versenden im Kontext des Hauptthreads spezifiziert, wird Send
-Methode diese Anforderung berücksichtigen, sodass Sie sich nicht um die Synchronisierung in diesen Ereignishandlern kümmern müssen.
Ob Post
oder Send
Anrufe blockieren, hängt von den verwendeten Zustellungsoptionen ab. Bitte beachten Sie bei der Verwendung Post
, dass verschiedene Abonnements für denselben Veranstaltungstyp mit unterschiedlichen Zustellungsoptionen konfiguriert werden können.
TNxHorizon
ist eine manuell verwaltete, vollständig threadsichere Klasse. Sie können beliebig viele separate Event-Bus-Instanzen erstellen. Instanzen sind vollständig Thread-sicher und erfordern keinen zusätzlichen Schutz, solange Sie Referenzen im schreibgeschützten Modus verwenden. Sobald Sie die Referenz initialisiert und begonnen haben, diese Instanz threadübergreifend zu verwenden, dürfen Sie die Referenzvariable selbst nicht mehr ändern . Sie können von jedem Thread aus beliebige Methoden für eine solche Referenz aufrufen.
Wenn Sie verschiedene Kanäle unterstützen müssen (zusätzliche Ereigniskategorisierung), können Sie diese Funktionalität erreichen, indem Sie für jeden Kanal eine separate Ereignisbusinstanz erstellen.
Die Funktionalität der TNxHorizon
-Klasse kann nicht direkt als Schnittstelle verfügbar gemacht werden, da sie parametrisierte Methoden verwendet, die für Schnittstellen nicht unterstützt werden.
Neben der über NxHorizon.Instance
verfügbaren Singleton-Instanz ist es möglich, separate Businstanzen für andere Zwecke zu verwenden, mit viel kürzerer Lebensdauer. Um die Lebensverwaltung für diese Instanzen zu vereinfachen und den Zugriff auf baumelnde Zeiger in einer Multithreading-Umgebung zu vermeiden, können Sie INxHorizon
verwenden, um solche Ereignisbusinstanzen sicher zu speichern und freizugeben.
Dies eröffnet auch die Möglichkeit, Event-Bus-Instanzen zu verwenden, die als Dispatching-Mechanismus im Beobachtermuster recht leichtgewichtig sind, wobei das beobachtbare Subjekt seine INxHorizon
Referenz hält und offenlegt, an die sich Beobachter anhängen können. Beim Abonnieren sollten Beobachter INxHorizon
Instanz, die sie abonnieren, speichern, damit sie sich sicher davon abmelden können, auch wenn das Subjekt selbst in der Zwischenzeit freigegeben wurde.
Dies ermöglicht die Thread-sichere Verwendung des Beobachtermusters mit Subjekten, bei denen es sich nicht um automatisch verwaltete Instanzen handelt. Außerdem werden durch das direkte Halten starker (threadsicherer) Referenzen auf die Ereignisbusinstanz anstelle des Subjekts potenzielle Referenzzyklen vermieden, wenn verwaltete Objektinstanzen verwendet werden, anstatt threadunsichere schwache Referenzen zu verwenden.
INxHorizon.Instance
gibt eine umschlossene TNxHorizon
-Instanz zurück, die manuell von einem Container verwaltet wird. Es kann sicher verwendet werden, solange der Abonnent einen starken Verweis auf seinen Container hat.
Das Subjekt muss während des Bereinigungsprozesses ShutDown
-Methode für seine INxHorizon
Referenz aufrufen. Dadurch wird das IsActive
Flag auf False
gesetzt und TNxHorizonShutDownEvent
an seine Abonnenten gesendet, damit diese eine ordnungsgemäße Bereinigung durchführen können. TNxHorizonShutDownEvent
enthält eine umschlossene TNxHorizon
Instanz, sodass Abonnenten einen einzelnen Shutdown-Ereignishandler verwenden können, um mehrere Themen zu verwalten.
Der Aufruf ShutDown
hat keine Auswirkungen auf die Fähigkeit des Busses, Nachrichten zu senden und zu posten. Wenn Sie sicherstellen müssen, dass Sie während des Bereinigungsprozesses keine neuen Ereignisse versenden, können Sie IsActive
Flag überprüfen, bevor Sie Post
oder Send
aufrufen.
Dieser Ereignisbus nutzt TTask
aus der PPL für die asynchrone Verteilung von Ereignissen in XE7 und neueren Delphi-Versionen. Diese Aufgaben werden im Standard-Thread-Pool ausgeführt. Das ist beabsichtigt. Dies basiert auf der Prämisse, dass jeder Code, der den Standard-Thread-Pool verwendet, sehr schnell ausgeführt werden sollte und keine Konflikte verursachen sollte.
Wenn Sie Code in Ereignishandlern oder anderen Code haben, der den Standardpool für Aufgaben mit langer Laufzeit verwendet, die Probleme verursachen können, besteht die richtige Vorgehensweise darin, diesen spezifischen Code mit langer Laufzeit stattdessen in einem separaten, dedizierten Thread-Pool auszuführen Es besteht die Möglichkeit, überall mehrere Thread-Pools zu erstellen, die verschiedene Teile von Frameworks bedienen, die einige Aufgaben ausführen müssen.
Bei einem Ereignishandler mit langer Laufzeit besteht die schnellste Lösung des Problems darin, synchrones Dispatching zu verwenden und eine neue Aufgabe innerhalb des Ereignishandlercodes zu starten, die dann einen anderen, nicht standardmäßigen Thread-Pool verwenden kann. Auf diese Weise haben Sie mehr Kontrolle über Ihren Code und die Freiheit, das Verhalten eines bestimmten Handlers zu ändern, ohne dass dies Auswirkungen auf alle anderen Handler hat, die auf derselben Ereignisbusinstanz ausgeführt werden:
procedure TSubscriber.OnLongEvent ( const aEvent: TLongEvent);
begin
TTask.Run(
procedure
begin
...
end , DedicatedThreadPool);
end ;
Die Hauptmerkmale dieser Event-Bus-Implementierung sind Thread-Sicherheit, Geschwindigkeit und Einfachheit. Alle zusätzlichen Funktionen und Erweiterungen dürfen die ursprünglichen Ziele und Absichten nicht gefährden.
Diese Implementierung basiert auch auf meinen eigenen Anforderungen und meinem Code, und es ist möglich, dass einige Teile einen anderen gängigen Code-Workflow nicht vollständig erfüllen.
Da die Geschwindigkeit auf der aktuellen Implementierung der Post
und Send
-Methoden basiert, erwarte ich in diesen Bereichen keine großen Änderungen. Es ist jedoch möglich, andere Abonnement-Workflows außerhalb dieser beiden Methoden zu verbessern oder zu unterstützen.
https://dalija.prasnikar.info