TNxHorizon
클래스는 스레드로부터 완전히 안전합니다.NXHorizon.Instance
는 스레드로부터 안전하며 모든 스레드에서 사용할 수 있습니다. 이벤트 유형 선언:
이벤트는 유형 정보( TypeInfo
로 분류됩니다. 각각의 개별 이벤트 카테고리에는 고유한 유형이 필요합니다.
type
TFoo = class
...
end ;
TOtherFoo = type TFoo;
TIntegerEvent = type Integer;
TStringEvent = type string;
TFooEvent = INxEvent<TFoo>;
TOtherFooEvent = INxEvent<TOtherFoo>;
이벤트 구독/구독 취소:
이벤트 구독은 기존 클래스에 추가될 수 있습니다.
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 ;
메시지 보내기:
NxHorizon.Instance.Post<TIntegerEvent>( 5 );
NxHorizon.Instance.Send<TStringEvent>( ' abc ' , Async);
또는
var
IntEvent: TIntegerEvent;
StrEvent: TStringEvent;
IntEvent := 5 ;
StrEvent := ' abc ' ;
NxHorizon.Instance.Post(IntEvent);
NxHorizon.Instance.Send(StrEvent, Async);
이벤트 처리기 메서드는 다음 선언을 준수해야 합니다. 여기서 T
모든 유형이 될 수 있습니다. 비동기 전달에는 자동 메모리 관리 또는 값 유형이 있는 유형이 필요합니다. 수동으로 관리되고 수명이 긴 개체 인스턴스를 이벤트로 사용할 수도 있지만 이러한 경우 이미 전달된 메시지가 완전히 처리되기 전에 해당 인스턴스가 삭제되지 않도록 해야 합니다.
procedure( const aEvent: T) of object ;
TNxHorizonDelivery
유형은 네 가지 배달 옵션을 선언합니다.
Sync
- 현재 스레드에서 동기적입니다.Async
- 임의의 백그라운드 스레드에서 비동기MainSync
- 메인 스레드에서 동기식MainAsync
- 메인 스레드에서 비동기식 Sync
및 MainSync
는 BLOCKING 작업이며 이벤트 핸들러는 현재 스레드의 컨텍스트에서 즉시 실행되거나 기본 스레드와 동기화됩니다. 이렇게 하면 이벤트 핸들러가 완료될 때까지 동일한 이벤트 버스 인스턴스를 사용하여 다른 이벤트 전달이 차단됩니다. 기본 이벤트 버스 인스턴스에서는 사용하지 마십시오(또는 짧은 실행에만 아껴서 사용).
이벤트 전송이 메인 스레드의 컨텍스트에서 수행되면 MainAsync
전달은 TThread.ForceQueue
사용하여 메인 스레드의 컨텍스트에서 이벤트 핸들러를 비동기적으로 실행합니다.
이벤트 핸들러를 구독하면 새로운 INxEventSubscription
인스턴스가 생성됩니다. 나중에 구독을 취소하려면 반환된 인스턴스를 저장해야 합니다.
구독 취소에는 Unsubscribe
및 UnsubscribeAsync
두 가지 방법이 있습니다.
두 가지 방법 모두 구독을 취소하고 이벤트 버스에 유지되는 구독 컬렉션에서 제거합니다. 이 컬렉션은 Post
및 Send
메서드 내에서 반복됩니다. 이때 어떠한 수정도 허용되지 않으며 예기치 않은 동작이 발생할 수 있습니다.
반복 중에 구독자 컬렉션 수정을 방지하려면 동기적으로 전달된 이벤트 핸들러에서 실행되는 코드에서 구독을 취소하려는 경우 구독을 즉시 취소하지만 컬렉션 외부에서 실행하여 컬렉션에서 실제 제거를 지연시키는 UnsubscribeAsync
를 사용해야 합니다. 반복을 파견합니다.
비동기적으로 전달된 이벤트 처리기는 항상 전달 반복 외부에서 실행되며 Unsubscribe
메서드 사용을 허용합니다. 그러나 핸들러가 디스패치되는 방법은 관련 없는 외부 코드에 의해 변경될 수 있으며, 비동기 디스패치를 절대적으로 보장할 수 없는 경우 UnsubscribeAsync
사용하는 것이 보증됩니다.
Unsubscribe
및 UnsubscribeAsync
도 구독 컬렉션에서 구독을 제거하기 전에 구독을 취소합니다. 일반적으로 구독을 취소하기 전에 명시적으로 구독을 취소할 필요는 없지만, 구독을 취소하기 전 특정 시점에 구독을 취소하려는 특별한 이유가 있는 경우 Cancel
메서드를 호출할 수 있습니다. Cancel
여러 번 안전하게 호출할 수 있습니다. 구독이 취소되면 상태를 되돌릴 수 없습니다.
비동기 이벤트 전달로 인해 특정 구독을 취소하거나 구독 취소할 때 이미 전달된 이벤트 핸들러가 있을 수 있습니다. 소멸자(구독자 클래스 소멸자)의 구독을 취소하는 경우 이로 인해 소멸 프로세스 중에 또는 소멸된 후에 구독자 인스턴스에 액세스할 수 있습니다. 이러한 시나리오를 방지하려면 구독에서 WaitFor
호출하면 구독이 즉시 취소되고 전달된 모든 이벤트 핸들러의 실행이 완료될 때까지 차단됩니다.
기본 스레드의 컨텍스트에서 WaitFor
호출하고 이벤트 핸들러가 오랫동안 실행되면 애플리케이션이 해당 시간 동안 응답을 중지하게 됩니다.
BeginWork
및 EndWork
메서드는 구독 대기 메커니즘의 일부입니다. 다른 스레드의 이벤트 핸들러 내에서 일부 코드를 실행해야 하고 코드도 대기해야 하는 경우 해당 스레드를 시작하기 전에 BeginWork
호출하고 스레드가 완료된 후에 EndWork
호출할 수 있습니다. 모든 코드 경로가 결국 일치하는 EndWork
호출하는지 확인하세요. 그렇게 하지 않으면 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);
Post
메소드는 이벤트를 구독하는 동안 설정된 구독 전달 옵션에 따라 전달 옵션이 달라지는 이벤트를 게시하는 데 사용됩니다.
Send
메서드는 구독 전달 옵션을 재정의하고 전달된 aDelivery
매개 변수에 의해 결정된 방식으로 이벤트를 전달합니다. 구독이 기본 스레드의 컨텍스트에서 디스패치를 지정한 경우 Send
메서드는 해당 요구 사항을 따르므로 해당 이벤트 처리기의 동기화에 대해 걱정할 필요가 없습니다.
Post
또는 Send
통화를 차단할지 여부는 사용된 전달 옵션에 따라 다릅니다. Post
사용할 때 동일한 이벤트 유형에 대한 다양한 구독을 다양한 전달 옵션으로 구성할 수 있다는 점에 유의하세요.
TNxHorizon
은 수동으로 관리되며 스레드로부터 완전히 안전한 클래스입니다. 원하는 만큼 별도의 이벤트 버스 인스턴스를 생성할 수 있습니다. 인스턴스는 완전히 스레드로부터 안전하며 읽기 전용 모드에서 참조를 사용하는 한 추가 보호가 필요하지 않습니다. 일단 참조를 초기화하고 스레드 전체에서 해당 인스턴스를 사용하기 시작하면 참조 변수 자체를 수정할 수 없습니다. . 모든 스레드에서 이러한 참조에 대한 모든 메서드를 자유롭게 호출할 수 있습니다.
다양한 채널(추가 이벤트 분류)을 지원해야 하는 경우 각 채널에 대해 별도의 이벤트 버스 인스턴스를 생성하여 이러한 기능을 구현할 수 있습니다.
TNxHorizon
클래스의 기능은 인터페이스에서 지원되지 않는 매개변수화된 메서드를 사용하기 때문에 인터페이스로 직접 노출될 수 없습니다.
NxHorizon.Instance
를 통해 사용할 수 있는 싱글톤 인스턴스 외에도 훨씬 짧은 수명으로 다른 목적으로 별도의 버스 인스턴스를 사용할 수 있습니다. 해당 인스턴스의 수명 관리를 단순화하고 멀티스레딩 환경에서 매달려 있는 포인터에 액세스하지 않도록 INxHorizon
사용하여 해당 이벤트 버스 인스턴스를 안전하게 유지하고 공유할 수 있습니다.
이는 또한 관찰 가능한 주체가 관찰자가 연결할 수 있는 INxHorizon
참조를 보유하고 노출하는 관찰자 패턴 의 디스패칭 메커니즘으로 다소 가벼운 이벤트 버스 인스턴스를 사용할 수 있는 가능성을 열어줍니다. 구독할 때 관찰자는 구독 중인 INxHorizon
인스턴스를 저장해야 합니다. 그러면 그 동안 주체 자체가 해제되더라도 안전하게 구독을 취소할 수 있습니다.
이를 통해 자동으로 관리되는 인스턴스가 아닌 주제에 대해 스레드로부터 안전한 방식으로 관찰자 패턴을 사용할 수 있습니다. 또한 주체 대신 이벤트 버스 인스턴스에 대한 강력한(스레드 안전) 참조를 직접 유지하면 스레드 안전하지 않은 약한 참조를 사용하는 대신 관리 개체 인스턴스를 사용할 때 잠재적인 참조 순환을 피할 수 있습니다.
INxHorizon.Instance
컨테이너에서 수동으로 관리되는 래핑된 TNxHorizon
인스턴스를 반환합니다. 구독자가 해당 컨테이너에 대해 강력한 참조를 갖고 있는 한 안전하게 사용할 수 있습니다.
주체는 정리 프로세스 중에 INxHorizon
참조에서 ShutDown
메서드를 호출해야 합니다. 그러면 IsActive
플래그가 False
로 설정되고 TNxHorizonShutDownEvent
구독자에게 전송되어 적절한 정리를 수행할 수 있습니다. TNxHorizonShutDownEvent
에는 래핑된 TNxHorizon
인스턴스가 포함되어 있으므로 구독자는 단일 종료 이벤트 핸들러를 사용하여 여러 주제를 관리할 수 있습니다.
ShutDown
호출해도 메시지를 보내고 게시하는 버스 기능에는 아무런 영향이 없습니다. 정리 프로세스 중에 새 이벤트를 전달하지 않는지 확인해야 하는 경우 Post
또는 Send
호출하기 전에 IsActive
플래그를 확인할 수 있습니다.
이 이벤트 버스는 XE7 및 최신 Delphi 버전에서 이벤트를 비동기적으로 전달하기 위해 PPL의 TTask
활용합니다. 이러한 작업은 기본 스레드 풀에서 실행됩니다. 이것은 의도적으로 설계된 것입니다. 이는 기본 스레드 풀을 사용하는 모든 코드가 매우 빠르게 실행되어야 하며 경합을 유발해서는 안 된다는 전제를 기반으로 합니다.
문제를 일으킬 수 있는 장기 실행 작업에 대해 기본 풀을 사용하는 이벤트 핸들러 또는 기타 코드에 코드가 있는 경우 올바른 조치는 해당 특정 장기 실행 코드를 별도의 전용 스레드 풀에서 실행하는 것입니다. 일부 작업을 실행해야 하는 프레임워크의 다양한 부분을 서비스하는 여러 스레드 풀을 생성하는 것입니다.
장기 실행 이벤트 핸들러의 경우 문제에 대한 가장 빠른 해결책은 동기식 디스패치를 사용하고 이벤트 핸들러 코드 내에서 새 작업을 시작하여 기본이 아닌 다른 스레드 풀을 사용할 수 있는 것입니다. 이렇게 하면 코드를 더 잘 제어할 수 있고 동일한 이벤트 버스 인스턴스에서 실행되는 다른 모든 핸들러에 영향을 주지 않고 특정 핸들러의 동작을 자유롭게 변경할 수 있습니다.
procedure TSubscriber.OnLongEvent ( const aEvent: TLongEvent);
begin
TTask.Run(
procedure
begin
...
end , DedicatedThreadPool);
end ;
이 이벤트 버스 구현의 주요 특징은 스레드 안전성, 속도 및 단순성입니다. 추가 기능과 확장은 원래 목표와 의도를 손상해서는 안 됩니다.
이 구현 역시 내 요구 사항과 코드를 기반으로 하며 일부 부분이 다른 일반적인 코드 작업 흐름을 완전히 충족하지 못할 수도 있습니다.
속도는 Post
및 Send
메서드의 현재 구현을 기반으로 하기 때문에 해당 영역에서는 많은 변화가 없을 것으로 예상됩니다. 그러나 이 두 가지 방법 외에 다른 구독 워크플로를 개선하거나 지원하는 것은 가능합니다.
https://dalija.prasnikar.info