8


6

これが私の問題の基本的な要旨です。

  1. 私のメインウィンドウクラスはクラスAをインスタンス化します

  2. クラスAは、*セカンダリAppDomain *でクラスBをインスタンス化します。

  3. クラスBはイベントを発生させ、クラスAはそのイベントを正常に処理します。

  4. クラスAは独自のイベントを発生させます。

*問題:*ステップ4で、クラスAがクラスBのイベントをキャッチしたイベントハンドラメソッドから_own_イベントを発生させると、そのイベントが発生します。ただし、Windowクラスのサブスクライブハンドラは呼び出されません。

スローされている例外はありません。 セカンダリAppDomainを削除した場合、イベントは問題なく処理されます。

誰もがこれがうまくいかない理由を知っていますか? コールバックを使わずにこれを機能させる他の方法はありますか?

どちらにしても、問題はステップ4ではなくステップ3で発生すると思います。

これが問題を説明するための実際のコードサンプルです。

クラスWindow1

Private WithEvents _prog As DangerousProgram

Private Sub Button1_Click(System.ObjectとしてByVal、System.Windows.RoutedEventArgsとしてByVal e)Button1.Clickを処理します。

プライベートSub MyEventHandler(ByValオブジェクトを送信者として、ByVal eをNameChangedEventArgsとして処理)_prog.NameChanged TextBox1.Text = "プログラムの名前は現在:"

終了クラス

_パブリッククラスDangerousProgram

AppDomainとしてのプライベート_appDomainプログラムとしてのプライベートWithEvents _dangerousProgramパブリックイベントNameChanged(オブジェクトとしてのByValセンダー、名前としてのByVal e)

公開サブNew()

// DangerousProgramはセキュリティのために独自のAppDomain内に作成されます。

_appDomain = AppDomain.CreateDomain( "AppDomain")StringとしてのDimアセンブリ= System.Reflection.Assembly.GetEntryAssembly()。FullName _dangerousProgram = CType(_ _appDomain.CreateInstanceAndUnwrap(assembly、_ GetType(Program).FullName)、Program)

終了サブ

文字列としてのパブリックプロパティName()戻り値_dangerousProgram.Name終了取得セット(文字列としてのByVal値)_dangerousProgram.Name = value終了セット終了プロパティ

パブリックSub NameChangedHandler(ObjectとしてByValセンダ、NameChangedEventArgsとしてByVal e)_dangerousProgram.NameChangedを処理します。 プログラム名は{0}です。 "、e.Name))Debug.WriteLine("イベントの再発生中... ")

RaiseEvent NameChanged(私、新しいNameChangedEventArgs(e.Name))End Sub

終了クラス

_パブリッククラスプログラムはMarshalByRefObjectを継承

文字列としてのプライベート_nameパブリックイベントNameChanged(オブジェクトとしてのByVal送信者、名前としてのByVal e eChangedEventArgs)

文字列としてのPublicプロパティName()戻り値_name Endセット(文字列としてのByVal値)_name = value RaiseEvent NameChanged(私、New NameChangedEventArgs(_name))終了セットEndプロパティ

終了クラス

_パブリッククラスNameChangedEventArgs EventArgsを継承

文字列としてのパブリック名

Public Sub New(文字列としてのByVal newName)Name = newName End Sub

終了クラス

2 Answer


31


NETイベントの魔法は、AのインスタンスによってBのインスタンスのイベントを購読すると、AがBのアプリケーションドメインに送信されることを隠しています。 AがMarshalByRefでなければ、Aの値コピーが送信されます。 これで、Aのインスタンスが2つ別々になりました。これが、予期しない動作を経験した理由です。

誰かがこれがどのように起こるか理解するのに苦労しているならば、私はイベントがなぜこのように振る舞うかを明白にする以下の回避策を提案する。

実際のイベントを使用せずにB(appdomain 2内)で "events"を発生させ、A(appdomain 1内)でそれらを処理するには、メソッド呼び出しを変換する2つ目のオブジェクトを作成する必要があります。イベントに(あなたが期待するかもしれない方法を振る舞わない)。 このクラスは、Xと呼び、appdomain 1でインスタンス化され、そのプロキシはappdomain 2に送信されます。 これがコードです:

パブリッククラスX:MarshalByRefObject {パブリックイベントEventHandler MyEvent; public void FireEvent(){MyEvent(this、EventArgs.Empty); }}

擬似コードは次のようになります。

  1. * AD1 内の A は、新しいアプリケーションドメインを作成します。 それを AD2 *と呼びます。

  2. * A AD2 でCreateInstanceAndUnwrapを呼び出します。 * B *は AD2 に存在し、 B〜(proxy)〜 AD1 *に存在します。

  3. * A X *のインスタンスを作成します。

  4. * A X から B〜(プロキシ)〜*

  5. * AD2 では、 B X〜(proxy)〜 X *は MBROのインスタンスを持つ)

  6. * AD1 では、 A X.MyEvent *にイベントハンドラを登録します。

  7. * AD2 では、 B X〜(proxy)〜.FireEvent()を呼び出します。

  8. * AD1 では、_FireEvent_は X *上で実行され、これが_MyEvent_を起動します。

  9. FireEvent用の* Aの*イベントハンドラが実行されます。

    • B AD1 に戻ってイベントを起動するためには、メソッドだけでなくそのメソッドを起動するインスタンスも必要です。 だからこそ、 X のプロキシを AD2 *に送信する必要があります。 これはまた、クロスドメインイベントがイベントハンドラをドメイン境界を越えて整列化することを要求する理由でもあります!イベントは単にメソッド実行を囲む単なるラッパーです。 そのためには、メソッドだけでなくそれを実行するインスタンスも必要です。

経験則として、アプリケーションドメインの境界を越えてイベントを処理する場合は、両方のタイプ(イベントを公開するものとそれを処理するもの)がMarshalByRefObjectを継承する必要があります。


5


この問題を解決しようとする私の最初の試みでは、* Class B の `MarshalByRefObject`の継承を削除し、代わりに直列化可能としてそれにフラグを立てました。 その結果、オブジェクトは値によって整列化され、ホストAppDomainで実行される Class C *のコピーを取得しました。 これは私が望んでいたことではありません。

本当の解決策は、* Class B *(この例では DangerousProgram)も` MarshalByRefObject`を継承しなければならないので、*コールバックもプロキシを使用してスレッドをデフォルトのAppDomainに戻すことです。

ちなみに、 ここに素晴らしい記事があります私はrefと対でマーシャルを説明しているEric Lippertによって見つけました 非常に賢い方法で値によって整列化します。