8

这是我的问题的基本要点:

  1. 我的主窗口类实例化了 A 类。
  2. A 类在辅助 AppDomain中实例化 B 类。
  3. B 类引发事件,A 类成功处理该事件。
  4. A 类引发了它自己的事件。

问题:在第4步中,当A类从捕获B类事件的事件处理方法中引发自己的事件时,该事件被引发;但是,从不调用 Window 类中的订阅处理程序。

没有抛出异常。如果我删除辅助 AppDomain,则事件将毫无问题地得到处理。

有谁知道为什么这不起作用?有没有另一种方法可以在不使用回调的情况下完成这项工作?

我认为,如果有的话,问题将出现在第 3 步而不是第 4 步。

这是一个真实的代码示例来说明问题:

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 实例,这就是您遇到意外行为的原因。

如果有人很难理解这是如何发生的,我建议使用以下解决方法,这可以让事件以这种方式运行的原因变得显而易见。

为了在 B 中(在 appdomain 2 中)引发“事件”并在 A 中(在 appdomain 1 中)处理它们而不使用真实事件,我们需要创建第二个对象来转换方法调用(无需太多麻烦就可以跨越边界)事件(其行为与您的预期不同)。这个类,我们称之为 X,将在 appdomain 1 中实例化,其代理将被发送到 appdomain 2。代码如下:

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

伪代码将类似于:

  1. AAD1中创建一个新的 appdomain。称之为AD2
  2. A在AD2上调用 CreateInstanceAndUnwrap 。 B现在存在于AD2中,B (代理)存在于AD1中。
  3. A创建X的一个实例。
  4. A将X交给B代理
  5. AD2中,B现在有一个X (代理)实例(XMBRO
  6. AD1中,A向X.MyEvent注册了一个事件处理程序
  7. AD2中,B调用X (proxy) .FireEvent()
  8. AD1中,FireEvent在X上执行,这会触发MyEvent
  9. A 的FireEvent 事件处理程序执行。

为了让B在AD1中触发事件,它不仅必须具有方法,而且还必须具有触发该方法的实例。这就是我们必须将X的代理发送到AD2的原因。这也是为什么跨域事件需要跨域边界编组事件处理程序的原因! 事件只是方法执行的精美包装。为此,您不仅需要方法,还需要执行它的实例。

经验法则必须是,如果您希望跨应用程序域边界处理事件,这两种类型(一种公开事件和一种处理事件)都必须扩展 MarshalByRefObject。

于 2009-08-24T14:11:19.673 回答
5

在我第一次尝试解决这个问题时,我删除了Class B的继承MarshalByRefObject并将其标记为可序列化。结果是对象按值编组,我刚刚获得了在主机 AppDomain 中执行的C 类的副本。这不是我想要的。

我发现真正的解决方案是Class BDangerousProgram在示例中)也应该继承自,MarshalByRefObject以便回调也使用代理将线程转换回默认的 AppDomain。

顺便说一句,我在 Eric Lippert 找到了一篇很棒的文章,它以非常聪明的方式解释了 marshal by ref 与 marshal by value。

于 2009-08-15T11:30:05.843 回答