8

私の問題の基本的な要点は次のとおりです。

  1. 私のメイン Window クラスはクラス A をインスタンス化します。
  2. クラス A は、セカンダリ AppDomainでクラス B をインスタンス化します。
  3. クラス B がイベントを発生させ、クラス A がイベントを正常に処理します。
  4. クラス A は独自のイベントを発生させます。

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

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

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

どちらかといえば、問題はステップ 4 ではなくステップ 3 で発生すると思います。

問題を説明するための実際のコード サンプルを次に示します。

Class Window1

    Private WithEvents _prog As DangerousProgram    

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click    
        _prog = New DangerousProgram()
        _prog.Name = "Bad Program"  
    End Sub

    Private Sub MyEventHandler(ByVal sender As Object, ByVal e As NameChangedEventArgs) Handles _prog.NameChanged
        TextBox1.Text = "Program's name is now: " & e.Name
    End Sub

End Class


<Serializable()> _    
Public Class DangerousProgram

    Private _appDomain As AppDomain
    Private WithEvents _dangerousProgram As Program
    Public Event NameChanged(ByVal sender As Object, ByVal e As NameChangedEventArgs)


    Public Sub New()

        // DangerousPrograms are created inside their own AppDomain for security.

        _appDomain = AppDomain.CreateDomain("AppDomain")    
        Dim assembly As String = System.Reflection.Assembly.GetEntryAssembly().FullName 
        _dangerousProgram = CType( _   
                    _appDomain.CreateInstanceAndUnwrap(assembly, _    
                        GetType(Program).FullName), Program)

    End Sub


    Public Property Name() As String
        Get
            Return _dangerousProgram.Name
        End Get
        Set(ByVal value As String)
            _dangerousProgram.Name = value
        End Set
    End Property


    Public Sub NameChangedHandler(ByVal sender As Object, ByVal e As NameChangedEventArgs) Handles _dangerousProgram.NameChanged    
        Debug.WriteLine(String.Format("Caught event in DangerousProgram. Program name is {0}.", e.Name))
        Debug.WriteLine("Re-raising event...")

        RaiseEvent NameChanged(Me, New NameChangedEventArgs(e.Name))   
    End Sub

End Class


<Serializable()> _    
Public Class Program
    Inherits MarshalByRefObject

    Private _name As String
    Public Event NameChanged(ByVal sender As Object, ByVal e As NameChangedEventArgs)

    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
            RaiseEvent NameChanged(Me, New NameChangedEventArgs(_name))
        End Set
    End Property   

End Class


<Serializable()> _   
Public Class NameChangedEventArgs
    Inherits EventArgs

    Public Name As String

    Public Sub New(ByVal newName As String)
        Name = newName
    End Sub

End Class
4

2 に答える 2

32

.NET イベントの魔法は、A のインスタンスによって B のインスタンスのイベントにサブスクライブすると、A が B の appdomain に送信されるという事実を隠します。A が MarshalByRef でない場合、A の値のコピーが送信されます。これで、A の 2 つの別個のインスタンスが得られました。これが、予期しない動作を経験した理由です。

これがどのように発生するのか理解に苦しむ人がいる場合は、次の回避策をお勧めします。これにより、イベントがこのように動作する理由が明確になります。

B (appdomain 2 内) で「イベント」を発生させ、実際のイベントを使用せずに A (appdomain 1 内) でそれらを処理するには、メソッド呼び出しを変換する 2 番目のオブジェクトを作成する必要があります (これは、あまり手間をかけずに境界を越えます)。イベントに(期待どおりに動作しません)。このクラス (X と呼びましょう) は appdomain 1 でインスタンス化され、そのプロキシは appdomain 2 に送信されます。コードは次のとおりです。

public class X : MarshalByRefObject
{
  public event EventHandler MyEvent;
  public void FireEvent(){ MyEvent(this, EventArgs.Empty); }
}

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

  1. Aは、 AD1内で、新しい appdomain を作成します。AD2と呼びます。
  2. AはAD2で CreateInstanceAndUnwrap を呼び出します。 Bは現在AD2に存在し、B (プロキシ)はAD1に存在します。
  3. AはXのインスタンスを作成します。
  4. AがXBに渡す(代理)
  5. AD2では、BはX (プロキシ)のインスタンスを持つようになりました( XMBRO です) 。
  6. AD1で、Aはイベント ハンドラーをX.MyEventに登録します。
  7. AD2では、BがX (プロキシ) .FireEvent()を呼び出します。
  8. AD1では、FireEventがXで実行され、 MyEventが起動されます。
  9. FireEvent に対するA のイベント ハンドラが実行されます。

BがAD1でイベントを発生させるには、メソッドだけでなく、そのメソッドを起動するインスタンスも必要です。そのため、 XのプロキシをAD2送信する必要があります。これが、クロスドメイン イベントでイベント ハンドラーをドメイン境界を越えてマーシャリングする必要がある理由でもあります。 イベントは、メソッド実行の単なるラッパーです。そのためには、メソッドだけでなく、それを実行するインスタンスも必要です。

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

于 2009-08-24T14:11:19.673 に答える
5

この問題を解決するための最初の試みで、Class Bの継承を削除MarshalByRefObjectし、代わりにシリアライズ可能としてフラグを立てました。その結果、オブジェクトは値によってマーシャリングされ、ホスト AppDomain で実行されるクラス Cのコピーを取得しました。これは私が欲しかったものではありません。

私が見つけた本当の解決策は、クラス B (DangerousProgram例では) も継承する必要がMarshalByRefObjectあるため、コールバックでもプロキシを使用してスレッドをデフォルトの AppDomain に戻すことでした。

ところで、これはEric Lippert によって見つけられた素晴らしい記事で、参照によるマーシャルと値によるマーシャルを非常に巧妙な方法で説明しています。

于 2009-08-15T11:30:05.843 に答える