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

时间:2009-08-14 11:21:01

标签: .net events event-handling appdomain

以下是我的问题的基本要点:

  1. 我的主要Window类实例化A类。
  2. A类在辅助AppDomain 中实例化B类。
  3. B类引发一个事件,A类成功处理该事件。
  4. A类引发了自己的事件。
  5. 问题:在第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
    

2 个答案:

答案 0 :(得分:31)

.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); }
}

伪代码将类似于:

    AD1
  1. A 会创建一个新的应用域。称之为 AD2
  2. A AD2 上调用CreateInstanceAndUnwrap。现在 AD2 中存在 B AD1 中存在 B (代理) 。< / LI>
  3. A 会创建 X
  4. 的实例
  5. A 指针 X 指向 B (代理人)
  6. AD2 中, B 现在有一个 X (代理) 的实例( X < / strong>是MBRO
  7. AD1 中, A 使用 X.MyEvent
  8. 注册事件处理程序
  9. AD2 中, B 调用 X (代理) .FireEvent()
  10. AD1 中, FireEvent X 上执行,它会触发 MyEvent
  11. FireEvent的A 事件处理程序执行。
  12. 为了让 B AD1 中重新启动事件,它不仅必须具有该方法,还必须具有触发该方法的实例。这就是为什么我们必须将 X 的代理发送到 AD2 。这也是为什么跨域事件需要在域边界上编组事件处理程序的原因!事件只是方法执行的一个奇特的包装器。要做到这一点,你不仅需要方法,还需要实例来执行它。

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

答案 1 :(得分:5)

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

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

顺便说一句,here's a great article我发现Eric Lippert用一种非常聪明的方式通过ref和marshal解释了元帅。