.NET:使用 AppDomains 引发和处理事件时出现问题
这是我的问题的基本要点:
- 我的主 Window 类实例化 A 类。A
- 类在辅助 AppDomain 中实例化 B 类。
- B 类引发事件,A 类成功处理该事件。
- 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
Here is the basic gist of my problem:
- My main Window class instantiates Class A.
- Class A instantiates Class B in a secondary AppDomain.
- Class B raises an event and Class A handles the event successfully.
- Class A raises an event of its own.
Problem: In step 4, when Class A raises its own event from the event handler method that caught Class B's event, the event is raised; however, the subscribing handler in the Window class is never called.
There are no exceptions being thrown. If I remove the secondary AppDomain, the event gets handled without a problem.
Does anyone know why this doesn't work? Is there another way to make this work without using a callback?
I would think, if anything, the problem would occur in step 3 instead of step 4.
Here's a real code sample to illustrate the problem:
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
.NET 事件的魔力隐藏了这样一个事实:当您通过 A 的实例订阅 B 的实例中的事件时,A 会被发送到 B 的应用程序域中。 如果 A 不是 MarshalByRef,则发送 A 的值副本。 现在您已经有了 A 的两个独立实例,这就是您遇到意外行为的原因。
如果有人很难理解这是如何发生的,我建议使用以下解决方法,它可以清楚地说明为什么事件会发生这种情况。
为了在 B(在 appdomain 2 内)中引发“事件”并在 A(在 appdomain 1 内)中处理它们而不使用真实事件,我们需要创建第二个对象来转换方法调用(无需太多麻烦就可以跨越边界)事件(其行为与您预期的不同)。 这个类(我们称之为 X)将在 appdomain 1 中实例化,其代理将被发送到 appdomain 2 中。代码如下:
伪代码将类似于:
为了让 B 在 AD1 中触发事件,它不仅必须具有该方法,而且还必须具有触发该方法的实例。 这就是为什么我们必须将 X 代理发送到 AD2 中。 这也是为什么跨域事件需要跨域边界编组事件处理程序的原因!事件只是方法执行的一个奇特的包装器。 为此,您不仅需要方法,还需要执行该方法的实例。
经验法则必须是,如果您希望跨应用程序域边界处理事件,则两种类型(公开事件的类型和处理事件的类型)都必须扩展 MarshalByRefObject。
The magic of .NET events hides the fact that, when you subscribe to an event in an instance of B by an instance of A, A gets sent over into B's appdomain. If A isn't MarshalByRef, then a value-copy of A is sent. Now you've got two separate instances of A, which is why you experienced the unexpected behaviors.
If anyone is having a hard time understanding how this happens, I suggest the following workaround which makes it obvious why events behave this way.
In order to raise "events" in B (within appdomain 2) and handle them in A (within appdomain 1) without using real events, we'll need to create a second object which translates method calls (which cross boundaries without much ado) to events (which don't behave how you might expect). This class, lets call it X, will be instantiated in appdomain 1, and its proxy will be sent into appdomain 2. Here's the code:
The pseudocode would go something like:
In order for B to fire an event back in AD1, it not only must have the method but also an instance to fire that method on. That's why we have to send a proxy of X into AD2. This is also why cross-domain events require the event handler to be marshalled across the domain boundary! An event is just a fancy wrapper around a method execution. And to do that you need not only the method but also the instance to execute it on.
The rule of thumb must be that if you wish to handle events across an application domain boundary, both types--the one exposing the event and the one handling it--must extend MarshalByRefObject.
在我第一次尝试解决此问题时,我删除了 B 类 对
MarshalByRefObject
的继承,并将其标记为可序列化。 结果是对象按值封送,我刚刚获得了在主机 AppDomain 中执行的 C 类 的副本。 这不是我想要的。我发现,真正的解决方案是 B 类(示例中的
DangerousProgram
)也应该继承自MarshalByRefObject
,以便 调用back 还使用代理将线程转换回默认的 AppDomain。顺便说一句,这是一篇很棒的文章,我发现埃里克·利珀特(Eric Lippert)以非常聪明的方式解释了裁判元帅与价值元帅。
In my first attempt at solving this issue, I removed Class B's inheritance of
MarshalByRefObject
and flagged it as serializable instead. The result was the the object was marshaled by value and I just got a copy of Class C that executes in the host AppDomain. This is not what I wanted.The real solution, I found, was that Class B (
DangerousProgram
in the example) should also inherit fromMarshalByRefObject
so that the call back also uses a proxy to transition the thread back to the default AppDomain.By the way, here's a great article I found by Eric Lippert that explains marshal by ref vs. marshal by value in a very clever way.