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 T
任意の型です。非同期配信には、自動メモリ管理を備えた型または値型が必要です。手動で管理された有効期間の長いオブジェクト インスタンスをイベントとして使用することもできますが、その場合は、すでにディスパッチされたメッセージが完全に処理される前にインスタンスが破棄されないようにする必要があります。
procedure( const aEvent: T) of object ;
TNxHorizonDelivery
タイプは、4 つの配信オプションを宣言します。
Sync
- 現在のスレッドで同期Async
- ランダムなバックグラウンド スレッドでの非同期MainSync
- メインスレッドで同期MainAsync
- メインスレッドでの非同期Sync
とMainSync
ブロック操作であり、イベント ハンドラーは現在のスレッドのコンテキストですぐに実行されるか、メイン スレッドと同期されます。これにより、イベント ハンドラーが完了するまで、同じイベント バス インスタンスを使用する他のイベントのディスパッチがブロックされます。デフォルトのイベント バス インスタンスでは使用しないでください (または、短い実行の場合にのみ控えめに使用してください)。
イベントの送信がメイン スレッドのコンテキストから行われる場合、 MainAsync
配信はTThread.ForceQueue
使用して、メイン スレッドのコンテキストでイベント ハンドラーを非同期に実行します。
イベント ハンドラーをサブスクライブすると、新しいINxEventSubscription
インスタンスが構築されます。後でサブスクライブを解除するために、返されたインスタンスを保存する必要があります。
購読を解除するには、 Unsubscribe
とUnsubscribeAsync
の 2 つの方法があります。
どちらのメソッドもサブスクリプションをキャンセルし、イベント バス内で維持されているサブスクリプションのコレクションからサブスクリプションを削除します。このコレクションは、 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
メソッドの現在の実装に基づいているため、これらの領域での大きな変更は期待できません。ただし、これら 2 つの方法以外でも、さまざまなサブスクリプション ワークフローを改善またはサポートすることは可能です。
https://dalija.prasnikar.info