以下是我的问题的基本要点:
问题:在第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
答案 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); }
}
伪代码将类似于:
为了让 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解释了元帅。