.NET:使用AppDomains引发和处理事件的问题

Rob*_*ers 8 .net events appdomain event-handling

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

  1. 我的主要Window类实例化了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
Run Code Online (Sandbox Code Playgroud)

小智 31

.NET事件的神奇之处在于,当您通过A的实例订阅B实例中的事件时,A会被发送到B的appdomain中.如果A不是MarshalByRef,则发送A的值副本.现在你有两个独立的A实例,这就是你遇到意外行为的原因.

如果有人很难理解这是如何发生的,我建议采用以下解决方法,这使得事件以这种方式表现的原因显而易见.

为了在B中(在appdomain 2内)引发"事件"并在A中(在appdomain 1内)处理它们而不使用真实事件,我们需要创建第二个对象来转换方法调用(跨越边界而不用太多ado)事件(不按预期行事).这个类,我们称之为X,将在appdomain 1中实例化,其代理将被发送到appdomain 2.这是代码:

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

伪代码将类似于:

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

为了让BAD1中重新触发事件,它不仅必须具有该方法,还必须具有触发该方法的实例.这就是我们必须将代理X发送到AD2的原因.这也是跨域事件需要跨域边界编组事件处理程序的原因! 事件只是方法执行的一个奇特的包装器.要做到这一点,你不仅需要方法,还需要实例来执行它.

经验法则必须是,如果您希望跨应用程序域边界处理事件,则两种类型 - 暴露事件和处理事件的类型 - 必须扩展MarshalByRefObject.


Rob*_*ers 5

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

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

顺便说一下,这是 Eric Lippert发现的一篇很棒的文章,它以非常聪明的方式通过ref和marshal解释了元帅.