TNxHorizon est entièrement thread-safeNXHorizon.Instance , est thread-safe et peut être utilisée à partir de n'importe quel thread Déclarez le type d'événement :
Les événements sont classés par type d'informations - TypeInfo . Chaque catégorie d'événement distincte nécessite un type distinct.
type
TFoo = class
...
end ;
TOtherFoo = type TFoo;
TIntegerEvent = type Integer;
TStringEvent = type string;
TFooEvent = INxEvent<TFoo>;
TOtherFooEvent = INxEvent<TOtherFoo>;Inscription/désinscription à l'événement :
L'abonnement aux événements peut être ajouté à n'importe quelle classe existante.
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 ;Envoyer des messages :
NxHorizon.Instance.Post<TIntegerEvent>( 5 );
NxHorizon.Instance.Send<TStringEvent>( ' abc ' , Async);ou
var
IntEvent: TIntegerEvent;
StrEvent: TStringEvent;
IntEvent := 5 ;
StrEvent := ' abc ' ;
NxHorizon.Instance.Post(IntEvent);
NxHorizon.Instance.Send(StrEvent, Async); Les méthodes du gestionnaire d’événements doivent être conformes à la déclaration suivante, où T peut être de n’importe quel type. La livraison asynchrone nécessite des types avec une gestion automatique de la mémoire ou des types de valeur. Vous pouvez également utiliser des instances d'objets de longue durée gérées manuellement comme événements, mais dans de tels cas, vous devez vous assurer qu'elles ne seront pas détruites avant que les messages déjà envoyés ne soient entièrement traités.
procedure( const aEvent: T) of object ; Le type TNxHorizonDelivery déclare quatre options de livraison :
Sync - synchrone dans le thread actuelAsync - asynchrone dans un thread d'arrière-plan aléatoireMainSync - synchrone sur le thread principalMainAsync - asynchrone sur le thread principal Sync et MainSync sont des opérations BLOCANTES, et le gestionnaire d'événements s'exécutera immédiatement dans le contexte du thread actuel, ou synchronisé avec le thread principal. Cela bloquera la distribution d'autres événements à l'aide de la même instance de bus d'événements jusqu'à ce que le gestionnaire d'événements ait terminé. Ne l'utilisez pas (ou utilisez-le avec parcimonie uniquement pour de courtes exécutions) sur l'instance de bus d'événements par défaut.
Si l'envoi d'événements est effectué à partir du contexte du thread principal, la livraison MainAsync utilisera TThread.ForceQueue pour exécuter le gestionnaire d'événements de manière asynchrone dans le contexte du thread principal.
L'abonnement à un gestionnaire d'événements construit une nouvelle instance INxEventSubscription . Vous devez stocker l'instance renvoyée afin de vous désinscrire ultérieurement.
Il existe deux méthodes de désabonnement : Unsubscribe et UnsubscribeAsync .
Les deux méthodes annulent l’abonnement et le suppriment de la collection d’abonnements gérée dans le bus d’événements. Cette collection est itérée dans les méthodes Post et Send . Toute modification à ce moment-là n'est pas autorisée et pourrait entraîner un comportement inattendu.
Pour éviter la modification de la collection d'abonnés pendant l'itération, si vous souhaitez vous désabonner du code exécuté dans un gestionnaire d'événements distribué de manière synchrone, vous devez utiliser UnsubscribeAsync , qui annulera immédiatement l'abonnement, mais retardera la suppression réelle de la collection, en l'exécutant en dehors du itération de répartition.
Les gestionnaires d'événements distribués de manière asynchrone s'exécutent toujours en dehors de l'itération de distribution et permettent d'utiliser la méthode Unsubscribe . Cependant, la manière dont les gestionnaires sont distribués peut être modifiée par un code externe non lié, et si vous ne pouvez pas garantir de manière absolue une distribution asynchrone, l'utilisation de UnsubscribeAsync est garantie.
Unsubscribe et UnsubscribeAsync annulent également l'abonnement, avant de le supprimer de la collection d'abonnements. Habituellement, il n'est pas nécessaire d'annuler explicitement l'abonnement avant de vous désinscrire, mais si vous avez une raison particulière pour laquelle vous souhaitez annuler l'abonnement à un moment donné avant de vous désinscrire, vous pouvez appeler sa méthode Cancel . Cancel peut être appelée plusieurs fois en toute sécurité. Une fois qu’un abonnement est annulé, son état ne peut plus être rétabli.
En raison de la répartition asynchrone des événements, il est possible qu'un gestionnaire d'événements soit déjà distribué au moment où vous annulez ou désabonnez un abonnement particulier. Si vous vous désabonnez d'un destructeur, votre destructeur de classe d'abonné, cela pourrait vous amener à accéder à l'instance d'abonné pendant son processus de destruction ou après sa destruction. Pour éviter un tel scénario, vous pouvez appeler WaitFor sur l'abonnement, ce qui annulera immédiatement l'abonnement et le bloquera jusqu'à ce que tous les gestionnaires d'événements envoyés aient fini de s'exécuter.
Si vous appelez WaitFor à partir du contexte du thread principal et que vos gestionnaires d'événements s'exécutent pendant une longue période, votre application cessera de répondre pendant cette période.
Les méthodes BeginWork et EndWork font partie du mécanisme d'attente d'abonnement. Si vous devez exécuter du code dans un gestionnaire d'événements dans un autre thread et que vous devez vous assurer que ce code sera également attendu, vous pouvez appeler BeginWork avant de démarrer un tel thread et EndWork une fois qu'il est terminé. Assurez-vous que tous les chemins de code finiront par appeler un EndWork correspondant, car ne pas le faire entraînera un blocage lorsque vous appellerez WaitFor .
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); La méthode Post est utilisée pour publier des événements où l'option de livraison dépendra de l'option de livraison de l'abonnement définie lors de l'abonnement à l'événement.
La méthode Send remplace l'option de remise de l'abonnement et distribue un événement d'une manière déterminée par le paramètre aDelivery transmis. Si l'abonnement spécifie la répartition dans le contexte du thread principal, la méthode Send respectera cette exigence, vous n'aurez donc pas à vous soucier de la synchronisation dans ces gestionnaires d'événements.
Le fait que Post ou Send bloque les appels dépend des options de livraison utilisées. Lorsque vous utilisez Post , veuillez noter que différents abonnements au même type d'événement peuvent être configurés avec différentes options de livraison.
TNxHorizon est une classe gérée manuellement et entièrement thread-safe. Vous pouvez créer autant d'instances de bus d'événements distinctes que vous le souhaitez. Les instances sont entièrement thread-safe et ne nécessitent aucune protection supplémentaire tant que vous utilisez des références en mode lecture seule. Une fois que vous avez initialisé la référence et commencé à utiliser cette instance dans plusieurs threads, vous n'êtes pas autorisé à modifier la variable de référence elle-même. . Vous pouvez librement appeler n'importe quelle méthode sur une telle référence à partir de n'importe quel thread.
Si vous devez prendre en charge différents canaux (catégorisation d'événements supplémentaire), vous pouvez obtenir cette fonctionnalité en créant une instance de bus d'événements distincte pour chaque canal.
La fonctionnalité de la classe TNxHorizon ne peut pas être directement exposée en tant qu'interface car elle utilise des méthodes paramétrées qui ne sont pas prises en charge pour les interfaces.
Outre l'instance singleton disponible via NxHorizon.Instance il est possible d'utiliser des instances de bus distinctes à d'autres fins, avec une durée de vie beaucoup plus courte. Afin de simplifier la gestion de la durée de vie de ces instances et d'éviter d'accéder à des pointeurs en suspens dans un environnement multithread, vous pouvez utiliser INxHorizon pour conserver et partager en toute sécurité de telles instances de bus d'événements.
Cela ouvre également la possibilité d'utiliser des instances de bus d'événements, qui sont plutôt légères en tant que mécanisme de répartition dans le modèle d'observateur , où le sujet observable détient et expose sa référence INxHorizon , à laquelle les observateurs peuvent s'attacher. Lors de l'abonnement, les observateurs doivent stocker l'instance INxHorizon à laquelle ils sont abonnés, afin de pouvoir s'en désabonner en toute sécurité, même si le sujet lui-même a été publié entre-temps.
Cela permet d'utiliser le modèle d'observateur de manière thread-safe avec des sujets qui ne sont pas des instances automatiquement gérées. De plus, le fait de conserver une référence forte (thread-safe) à l'instance de bus d'événements au lieu du sujet évite directement les cycles de référence potentiels lors de l'utilisation d'instances d'objet géré, au lieu d'utiliser des références faibles non sécurisées pour les threads.
INxHorizon.Instance renvoie une instance TNxHorizon encapsulée qui est gérée manuellement par un conteneur. Il peut être utilisé en toute sécurité tant que l’abonné fait fortement référence à son conteneur.
Le sujet doit appeler la méthode ShutDown sur sa référence INxHorizon lors de son processus de nettoyage. Cela définira l'indicateur IsActive sur False et enverra TNxHorizonShutDownEvent à ses abonnés, afin qu'ils puissent effectuer un nettoyage approprié. TNxHorizonShutDownEvent contient une instance TNxHorizon encapsulée, afin que les abonnés puissent utiliser un seul gestionnaire d'événements d'arrêt pour gérer plusieurs sujets.
L’appel ShutDown n’a aucun impact sur la capacité du bus à envoyer et publier des messages. Si vous devez vous assurer que vous ne distribuez pas de nouveaux événements pendant le processus de nettoyage, vous pouvez vérifier l'indicateur IsActive avant d'appeler Post ou Send .
Ce bus d'événements utilise TTask du PPL pour la répartition asynchrone des événements dans XE7 et les versions Delphi plus récentes. Ces tâches s'exécutent sur le pool de threads par défaut. C'est par conception. Ceci est basé sur le principe selon lequel tout code utilisant le pool de threads par défaut doit s'exécuter très rapidement et ne doit pas provoquer de conflits.
Si vous avez du code dans les gestionnaires d'événements ou un autre code qui utilise le pool par défaut pour des tâches de longue durée susceptibles de causer des problèmes, la bonne marche à suivre consiste à exécuter ce code spécifique de longue durée sur un pool de threads distinct et dédié. de créer plusieurs pools de threads partout qui serviront différentes parties des frameworks qui doivent exécuter certaines tâches.
Pour un gestionnaire d'événements de longue durée, la solution la plus rapide au problème consiste à utiliser la répartition synchrone et à démarrer une nouvelle tâche dans le code du gestionnaire d'événements qui peut ensuite utiliser un autre pool de threads non par défaut. De cette façon, vous aurez plus de contrôle sur votre code et la liberté de modifier le comportement d'un gestionnaire spécifique sans affecter tous les autres gestionnaires exécutés sur la même instance de bus d'événements :
procedure TSubscriber.OnLongEvent ( const aEvent: TLongEvent);
begin
TTask.Run(
procedure
begin
...
end , DedicatedThreadPool);
end ;Les principales caractéristiques de cette implémentation de bus d'événements sont la sécurité des threads, la vitesse et la simplicité. Les fonctionnalités et extensions supplémentaires ne doivent pas compromettre ces objectifs et intentions d’origine.
Cette implémentation est également basée sur mes propres exigences et mon code, et il est possible que certaines parties ne satisfassent pas pleinement à un autre flux de travail de code courant.
Étant donné que la vitesse est basée sur la mise en œuvre actuelle des méthodes Post et Send , je ne m'attends pas à beaucoup de changements dans ces domaines. Cependant, il est possible d'améliorer ou de prendre en charge différents flux de travail d'abonnement en dehors de ces deux méthodes.
https://dalija.prasnikar.info