.NET:使用 AppDomains 引发和处理事件时出现问题

发布于 2024-08-01 19:35:11 字数 2950 浏览 6 评论 0原文

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

  1. 我的主 Window 类实例化 A 类。A
  2. 类在辅助 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

Here is the basic gist of my problem:

  1. My main Window class instantiates Class A.
  2. Class A instantiates Class B in a secondary AppDomain.
  3. Class B raises an event and Class A handles the event successfully.
  4. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我的影子我的梦 2024-08-08 19:35:11

.NET 事件的魔力隐藏了这样一个事实:当您通过 A 的实例订阅 B 的实例中的事件时,A 会被发送到 B 的应用程序域中。 如果 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. A,位于 AD1 中,创建一个新的应用程序域。 称之为AD2
  2. AAD2 上调用CreateInstanceAndUnwrap。 B 现在存在于 AD2 中,B(代理) 存在于 AD1 中。
  3. A 创建X 的实例。
  4. AX 交给 B(代理)
  5. AD2 中,B 现在有一个 X(proxy) 实例(XMBRO)
  6. AD1 中,A 向 < strong>X.MyEvent
  7. AD2中,B调用X(proxy).FireEvent()
  8. AD1中,FireEventX上执行,这会触发MyEvent
  9. A的事件处理程序FireEvent 执行。

为了让 BAD1 中触发事件,它不仅必须具有该方法,而且还必须具有触发该方法的实例。 这就是为什么我们必须将 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:

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

The pseudocode would go something like:

  1. A, within AD1, creates a new appdomain. Call it AD2.
  2. A calls CreateInstanceAndUnwrap on AD2. B now exists in AD2 and B(proxy) exists in AD1.
  3. A creates an instance of X.
  4. A hands X to B(proxy)
  5. In AD2, B now has an instance of X(proxy) (X is MBRO)
  6. In AD1, A registers an event handler with X.MyEvent
  7. In AD2, B calls X(proxy).FireEvent()
  8. In AD1, FireEvent executes on X, which fires MyEvent
  9. A's event handler for FireEvent executes.

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.

冷︶言冷语的世界 2024-08-08 19:35:11

在我第一次尝试解决此问题时,我删除了 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 from MarshalByRefObject 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文