TNxHorizon
es totalmente segura para subprocesosNXHorizon.Instance
, es segura para subprocesos y se puede utilizar desde cualquier subproceso Declarar tipo de evento:
Los eventos se clasifican por tipo de información: TypeInfo
. Cada categoría de evento separada requiere un tipo distinto.
type
TFoo = class
...
end ;
TOtherFoo = type TFoo;
TIntegerEvent = type Integer;
TStringEvent = type string;
TFooEvent = INxEvent<TFoo>;
TOtherFooEvent = INxEvent<TOtherFoo>;
Suscríbete/cancela suscripción al evento:
La suscripción a eventos se puede agregar a cualquier clase existente.
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 ;
Enviar mensajes:
NxHorizon.Instance.Post<TIntegerEvent>( 5 );
NxHorizon.Instance.Send<TStringEvent>( ' abc ' , Async);
o
var
IntEvent: TIntegerEvent;
StrEvent: TStringEvent;
IntEvent := 5 ;
StrEvent := ' abc ' ;
NxHorizon.Instance.Post(IntEvent);
NxHorizon.Instance.Send(StrEvent, Async);
Los métodos del controlador de eventos deben ajustarse a la siguiente declaración, donde T
puede ser de cualquier tipo. La entrega asincrónica requiere tipos con administración automática de memoria o tipos de valores. También puede utilizar instancias de objetos de larga duración administradas manualmente como eventos, pero en tales casos, debe asegurarse de que no se destruirán antes de que los mensajes ya enviados se procesen por completo.
procedure( const aEvent: T) of object ;
El tipo TNxHorizonDelivery
declara cuatro opciones de entrega:
Sync
: sincrónica en el hilo actualAsync
: asíncrono en un hilo de fondo aleatorioMainSync
: sincrónico en el hilo principalMainAsync
: asíncrono en el hilo principal Sync
y MainSync
son operaciones de BLOQUEO y el controlador de eventos se ejecutará inmediatamente en el contexto del hilo actual o se sincronizará con el hilo principal. Esto bloqueará el envío de otros eventos utilizando la misma instancia del bus de eventos hasta que se complete el controlador de eventos. No lo use (o utilícelo con moderación solo para ejecuciones cortas) en la instancia del bus de eventos predeterminado.
Si el envío de eventos se realiza desde el contexto del hilo principal, la entrega MainAsync
utilizará TThread.ForceQueue
para ejecutar el controlador de eventos de forma asíncrona en el contexto del hilo principal.
La suscripción a un controlador de eventos construye una nueva instancia INxEventSubscription
. Debe almacenar la instancia devuelta para poder cancelar la suscripción más adelante.
Hay dos métodos para cancelar la suscripción: Unsubscribe
y UnsubscribeAsync
.
Ambos métodos cancelan la suscripción y la eliminan de la colección de suscripciones mantenidas en el bus de eventos. Esta colección se itera dentro de los métodos Post
y Send
. No se permiten modificaciones en ese momento y podrían dar lugar a un comportamiento inesperado.
Para evitar la modificación de la colección de suscriptores durante la iteración, si desea cancelar la suscripción al código que se ejecuta en un controlador de eventos distribuido sincrónicamente, debe usar UnsubscribeAsync
, que cancelará inmediatamente la suscripción, pero retrasará la eliminación real de la colección, ejecutándola fuera del iteración de despacho.
Los controladores de eventos distribuidos de forma asíncrona siempre se ejecutan fuera de la iteración de envío y permiten utilizar el método Unsubscribe
. Sin embargo, la forma en que se distribuyen los controladores se puede cambiar mediante código externo no relacionado y, si no se puede garantizar absolutamente el envío asincrónico, se justifica el uso de UnsubscribeAsync
.
Unsubscribe
y UnsubscribeAsync
también cancelan la suscripción antes de eliminarla de la colección de suscripciones. Por lo general, no es necesario cancelar explícitamente la suscripción antes de cancelar la suscripción, pero si tiene alguna razón particular por la que desea cancelar la suscripción en algún momento antes de cancelar la suscripción, puede llamar a su método Cancel
. Cancel
se puede llamar de forma segura varias veces. Una vez que se cancela una suscripción, su estado no se puede revertir.
Debido al envío de eventos asíncrono, es posible tener un controlador de eventos ya enviado en el momento en que cancela o cancela la suscripción a una suscripción en particular. Si cancela su suscripción a un destructor, su destructor de clase de suscriptor, esto podría hacer que acceda a la instancia del suscriptor durante su proceso de destrucción o después de que haya sido destruida. Para evitar tal escenario, puede llamar WaitFor
en la suscripción, lo que cancelará inmediatamente la suscripción y la bloqueará hasta que todos los controladores de eventos enviados hayan terminado de ejecutarse.
Si llama WaitFor
desde el contexto del hilo principal y sus controladores de eventos se ejecutan durante mucho tiempo, esto hará que su aplicación deje de responder durante ese período de tiempo.
Los métodos BeginWork
y EndWork
son parte del mecanismo de espera de suscripción. Si necesita ejecutar algún código dentro de un controlador de eventos en algún otro hilo, y necesita asegurarse de que también se espere ese código, puede llamar BeginWork
antes de iniciar dicho hilo y EndWork
después de que finalice. Asegúrese de que todas las rutas de código eventualmente llamen a un EndWork
coincidente, ya que no hacerlo provocará un punto muerto cuando llame 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);
El método Post
se utiliza para publicar eventos donde la opción de entrega dependerá de la opción de entrega de la suscripción establecida al suscribirse al evento.
El método Send
anula la opción de entrega de suscripción y envía un evento de la manera determinada por el parámetro aDelivery
pasado. Si la suscripción especificaba el envío en el contexto del hilo principal, el método Send
cumplirá con ese requisito, por lo que no tiene que preocuparse por la sincronización en esos controladores de eventos.
El hecho de que Post
o Send
bloquee las llamadas depende de las opciones de entrega utilizadas. Cuando utiliza Post
, tenga en cuenta que se pueden configurar diferentes suscripciones al mismo tipo de evento con diferentes opciones de entrega.
TNxHorizon
es una clase totalmente segura para subprocesos y administrada manualmente. Puede crear tantas instancias de bus de eventos independientes como desee. Las instancias son completamente seguras para subprocesos y no requieren ninguna protección adicional siempre que use referencias en modo de solo lectura; una vez que inicialice la referencia y comience a usar esa instancia en todos los subprocesos, no podrá modificar la variable de referencia en sí. . Puede llamar libremente a cualquier método en dicha referencia desde cualquier hilo.
Si necesita admitir diferentes canales (categorización de eventos adicional), puede lograr dicha funcionalidad creando una instancia de bus de eventos separada para cada canal.
La funcionalidad de la clase TNxHorizon
no se puede exponer directamente como interfaz porque utiliza métodos parametrizados que no son compatibles con las interfaces.
Además de la instancia singleton disponible a través de NxHorizon.Instance
es posible utilizar instancias de bus independientes para otros fines, con una vida útil mucho más corta. Para simplificar la gestión de la vida de esas instancias y evitar el acceso a punteros pendientes en un entorno de subprocesos múltiples, puede utilizar INxHorizon
para mantener y compartir de forma segura dichas instancias de bus de eventos.
Esto también abre la posibilidad de utilizar instancias de bus de eventos, que son bastante livianas como mecanismo de distribución en el patrón de observador , donde el sujeto observable mantiene y expone su referencia INxHorizon
, a la que los observadores pueden conectarse. Al suscribirse, los observadores deben almacenar la instancia INxHorizon
a la que se están suscribiendo, de modo que puedan cancelar la suscripción de manera segura incluso si el sujeto en sí se ha liberado mientras tanto.
Esto permite utilizar el patrón de observador de manera segura para subprocesos con sujetos que no son instancias administradas automáticamente. Además, mantener una referencia fuerte (segura para subprocesos) a la instancia del bus de eventos en lugar del sujeto evita directamente posibles ciclos de referencia cuando se usan instancias de objetos administrados, en lugar de usar referencias débiles que no son seguras para subprocesos.
INxHorizon.Instance
devuelve una instancia TNxHorizon
empaquetada que un contenedor administra manualmente. Se puede utilizar de forma segura siempre que el suscriptor tenga una fuerte referencia a su contenedor.
El sujeto debe llamar al método ShutDown
en su referencia INxHorizon
durante el proceso de limpieza. Esto establecerá el indicador IsActive
en False
y enviará TNxHorizonShutDownEvent
a sus suscriptores, para que puedan realizar una limpieza adecuada. TNxHorizonShutDownEvent
contiene una instancia TNxHorizon
empaquetada, por lo que los suscriptores pueden usar un controlador de eventos de apagado único para administrar múltiples asuntos.
Llamar ShutDown
no tiene ningún impacto en la capacidad del bus para enviar y publicar mensajes. Si necesita asegurarse de no enviar nuevos eventos durante el proceso de limpieza, puede verificar el indicador IsActive
antes de llamar a Post
o Send
.
Este bus de eventos utiliza TTask
de PPL para el envío asincrónico de eventos en XE7 y versiones más recientes de Delphi. Esas tareas se ejecutan en el grupo de subprocesos predeterminado. Esto es por diseño. Esto se basa en la premisa de que cualquier código que utilice el grupo de subprocesos predeterminado debe ejecutarse muy rápido y no debe causar contención.
Si tiene código en controladores de eventos u otro código que utiliza el grupo predeterminado para tareas de ejecución prolongada que pueden causar problemas, entonces el curso de acción correcto es ejecutar ese código específico de ejecución prolongada en un grupo de subprocesos dedicado e independiente. de crear múltiples grupos de subprocesos que servirán para diferentes partes de los marcos que necesitan ejecutar algunas tareas.
Para un controlador de eventos de larga duración, la solución más rápida al problema es utilizar el envío sincrónico e iniciar una nueva tarea dentro del código del controlador de eventos que luego puede usar algún otro grupo de subprocesos no predeterminado. De esa manera, tendrá más control sobre su código y la libertad de cambiar el comportamiento de un controlador específico sin afectar a todos los demás controladores que se ejecutan en la misma instancia del bus de eventos:
procedure TSubscriber.OnLongEvent ( const aEvent: TLongEvent);
begin
TTask.Run(
procedure
begin
...
end , DedicatedThreadPool);
end ;
Las características principales de esta implementación de bus de eventos son la seguridad, la velocidad y la simplicidad de los subprocesos. Las funciones y extensiones adicionales no deben comprometer esos objetivos e intenciones originales.
Esta implementación también se basa en mis propios requisitos y código, y es posible que algunas partes no satisfagan completamente algún otro flujo de trabajo de código común.
Debido a que la velocidad se basa en la implementación actual de los métodos Post
y Send
, no espero muchos cambios en esas áreas. Sin embargo, es posible mejorar o admitir diferentes flujos de trabajo de suscripción fuera de esos dos métodos.
https://dalija.prasnikar.info