TNxHorizon é totalmente segura para threadsNXHorizon.Instance , é thread-safe e pode ser usada em qualquer thread Declarar tipo de evento:
Os eventos são categorizados por tipo de informação - TypeInfo . Cada categoria de evento separada requer um tipo distinto.
type
TFoo = class
...
end ;
TOtherFoo = type TFoo;
TIntegerEvent = type Integer;
TStringEvent = type string;
TFooEvent = INxEvent<TFoo>;
TOtherFooEvent = INxEvent<TOtherFoo>;Inscrever-se/cancelar inscrição no evento:
A inscrição em eventos pode ser adicionada a qualquer classe 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 ;Envie mensagens:
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); Os métodos do manipulador de eventos devem estar em conformidade com a declaração a seguir, onde T pode ser de qualquer tipo. A entrega assíncrona requer tipos com gerenciamento automático de memória ou tipos de valor. Você também pode usar instâncias de objetos de longa duração gerenciadas manualmente como eventos, mas, nesses casos, você deve garantir que elas não serão destruídas antes que as mensagens já enviadas sejam totalmente processadas.
procedure( const aEvent: T) of object ; O tipo TNxHorizonDelivery declara quatro opções de entrega:
Sync - síncrono no thread atualAsync - assíncrono em um thread aleatório em segundo planoMainSync - síncrono no thread principalMainAsync - assíncrono no thread principal Sync e MainSync são operações de BLOQUEIO e o manipulador de eventos será executado imediatamente no contexto do thread atual ou sincronizado com o thread principal. Isso bloqueará o envio de outros eventos usando a mesma instância do barramento de eventos até que o manipulador de eventos seja concluído. Não use-o (ou use-o com moderação apenas para execuções curtas) na instância padrão do barramento de eventos.
Se o envio de eventos for feito a partir do contexto do thread principal, a entrega MainAsync usará TThread.ForceQueue para executar o manipulador de eventos de forma assíncrona no contexto do thread principal.
A inscrição em um manipulador de eventos constrói uma nova instância INxEventSubscription . Você deve armazenar a instância retornada para cancelar a assinatura mais tarde.
Existem dois métodos para cancelar a assinatura: Unsubscribe e UnsubscribeAsync .
Ambos os métodos cancelam a assinatura e a removem da coleção de assinaturas mantida no barramento de eventos. Esta coleção está sendo iterada dentro dos métodos Post e Send . Quaisquer modificações nesse momento não são permitidas e podem resultar em comportamento inesperado.
Para evitar a modificação da coleção de assinantes durante a iteração, se você quiser cancelar a assinatura do código em execução em um manipulador de eventos despachado de forma síncrona, você deve usar UnsubscribeAsync , que cancelará imediatamente a assinatura, mas atrasará a remoção real da coleção, executando-a fora do despachando iteração.
Os manipuladores de eventos despachados de forma assíncrona sempre são executados fora da iteração de despacho e permitem o uso do método Unsubscribe . No entanto, a forma como os manipuladores são despachados pode ser alterada por código externo não relacionado e, se você não puder garantir absolutamente o despacho assíncrono, o uso de UnsubscribeAsync é garantido.
Unsubscribe e UnsubscribeAsync também cancelam a assinatura, antes de removê-la da coleção de assinaturas. Normalmente, não há necessidade de cancelar explicitamente a assinatura antes de cancelar, mas se você tiver algum motivo específico para cancelar a assinatura em algum momento antes de cancelar, você pode chamar seu método Cancel . Cancel pode ser chamado com segurança várias vezes. Depois que uma assinatura é cancelada, seu estado não pode ser revertido.
Devido ao envio assíncrono de eventos, é possível ter um manipulador de eventos já enviado no momento em que você cancela ou cancela a assinatura de uma assinatura específica. Se você estiver cancelando a assinatura de um destruidor, seu destruidor de classe de assinante, isso poderá fazer com que você acesse a instância do assinante durante seu processo de destruição ou após ela ter sido destruída. Para evitar tal cenário, você pode chamar WaitFor na assinatura, que cancelará imediatamente a assinatura e bloqueará até que todos os manipuladores de eventos despachados tenham concluído a execução.
Se você chamar WaitFor a partir do contexto do thread principal e seus manipuladores de eventos forem executados por um longo período, isso fará com que seu aplicativo pare de responder durante esse período.
Os métodos BeginWork e EndWork fazem parte do mecanismo de espera de assinatura. Se você precisar executar algum código dentro de um manipulador de eventos em algum outro thread e precisar ter certeza de que esse código também será esperado, você pode chamar BeginWork antes de iniciar esse thread e EndWork depois que ele terminar. Certifique-se de que todos os caminhos de código eventualmente chamarão um EndWork correspondente, pois não fazer isso causará um conflito quando você chamar 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); O método Post é usado para postar eventos onde a opção de entrega dependerá da opção de entrega da assinatura definida durante a assinatura do evento.
O método Send substitui a opção de entrega de assinatura e despacha um evento de maneira determinada pelo parâmetro aDelivery passado. Se a assinatura especificou o despacho no contexto do thread principal, o método Send atenderá a esse requisito, portanto, você não precisará se preocupar com a sincronização nesses manipuladores de eventos.
Se Post ou Send bloqueará chamadas depende das opções de entrega usadas. Ao usar Post , observe que diferentes assinaturas para o mesmo tipo de evento podem ser configuradas com diferentes opções de entrega.
TNxHorizon é uma classe gerenciada manualmente e totalmente segura para threads. Você pode criar quantas instâncias de barramento de eventos separadas desejar. As instâncias são totalmente thread-safe e não requerem nenhuma proteção adicional, desde que você use referências no modo somente leitura – depois de inicializar a referência e começar a usar essa instância entre threads, você não terá permissão para modificar a própria variável de referência . Você pode chamar livremente qualquer método nessa referência a partir de qualquer thread.
Se precisar oferecer suporte a diferentes canais (categorização adicional de eventos), você poderá obter essa funcionalidade criando uma instância de barramento de eventos separada para cada canal.
A funcionalidade da classe TNxHorizon não pode ser exposta diretamente como interface porque ela usa métodos parametrizados que não são suportados para interfaces.
Além da instância singleton disponível através do NxHorizon.Instance é possível usar instâncias de barramento separadas para outros fins, com vida útil muito mais curta. Para simplificar o gerenciamento da vida dessas instâncias e evitar o acesso a ponteiros pendentes em ambientes multithreading, você pode usar INxHorizon para armazenar e compartilhar com segurança essas instâncias de barramento de eventos.
Isso também abre a possibilidade de usar instâncias de barramento de eventos, que são bastante leves como mecanismo de despacho no padrão do observador , onde o sujeito observável mantém e expõe sua referência INxHorizon , à qual os observadores podem se anexar. Ao se inscrever, os observadores devem armazenar a instância INxHorizon que estão assinando, para que possam cancelar a assinatura dela com segurança, mesmo que o próprio assunto tenha sido liberado nesse meio tempo.
Isso permite usar o padrão observador de maneira segura para threads com assuntos que não são instâncias gerenciadas automaticamente. Além disso, manter uma referência forte (segura para thread) à instância do barramento de eventos em vez do assunto evita diretamente ciclos de referência potenciais ao usar instâncias de objetos gerenciados, em vez de usar referências fracas não seguras para thread.
INxHorizon.Instance retorna uma instância TNxHorizon empacotada que é gerenciada manualmente por um contêiner. Ele pode ser usado com segurança, desde que o assinante tenha uma referência forte ao seu contêiner.
O assunto precisa chamar o método ShutDown em sua referência INxHorizon durante o processo de limpeza. Isso definirá o sinalizador IsActive como False e enviará TNxHorizonShutDownEvent aos seus assinantes, para que eles possam realizar a limpeza adequada. TNxHorizonShutDownEvent contém uma instância TNxHorizon empacotada, para que os assinantes possam usar um único manipulador de eventos de desligamento para gerenciar vários assuntos.
Chamar ShutDown não tem nenhum impacto na capacidade do barramento de enviar e postar mensagens. Se precisar ter certeza de que não está despachando novos eventos durante o processo de limpeza, você pode verificar o sinalizador IsActive antes de chamar Post ou Send .
Este barramento de eventos utiliza TTask do PPL para envio assíncrono de eventos no XE7 e em versões mais recentes do Delphi. Essas tarefas são executadas no pool de threads padrão. Isso ocorre intencionalmente. Isso se baseia na premissa de que qualquer código que use o conjunto de threads padrão deve ser executado muito rapidamente e não deve causar contenção.
Se você tiver código em manipuladores de eventos ou outro código que use o pool padrão para tarefas de longa execução que possam causar problemas, o curso de ação correto será executar esse código específico e de longa execução em um pool de threads separado e dedicado. de criar vários pools de threads que servirão a diferentes partes de estruturas que precisam executar algumas tarefas.
Para um manipulador de eventos de longa execução, a solução mais rápida para o problema é usar o despacho síncrono e iniciar uma nova tarefa dentro do código do manipulador de eventos que pode então usar algum outro conjunto de threads não padrão. Dessa forma, você terá mais controle sobre seu código e a liberdade de alterar o comportamento de um manipulador específico sem impactar todos os outros manipuladores em execução na mesma instância do barramento de eventos:
procedure TSubscriber.OnLongEvent ( const aEvent: TLongEvent);
begin
TTask.Run(
procedure
begin
...
end , DedicatedThreadPool);
end ;Os principais recursos desta implementação de barramento de eventos são segurança de thread, velocidade e simplicidade. Quaisquer recursos e extensões adicionais não devem comprometer os objetivos e intenções originais.
Essa implementação também é baseada em meus próprios requisitos e código, e é possível que algumas partes não satisfaçam totalmente algum outro fluxo de trabalho de código comum.
Como a velocidade é baseada na implementação atual dos métodos Post e Send , não espero muitas mudanças nessas áreas. No entanto, é possível melhorar ou oferecer suporte a diferentes fluxos de trabalho de assinatura fora desses dois métodos.
https://dalija.prasnikar.info