.NET:使用 AppDomains 引發和處理事件的問題
這是我的問題的基本要點:
- 我的主視窗類實例化了 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
在我第一次嘗試解決這個問題時,我刪除了Class B的繼承
MarshalByRefObject並將其標記為可序列化。結果是對象按值編組,我剛剛獲得了在主機 AppDomain 中執行的**C 類的副本。**這不是我想要的。我發現真正的解決方案是Class B(
DangerousProgram在範例中)也應該繼承自,MarshalByRefObject以便回調也使用代理將執行緒轉換回預設的 AppDomain。順便說一句,我在 Eric Lippert 找到了一篇很棒的文章,它以非常聰明的方式解釋了 marshal by ref 與 marshal by value。
.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); } }虛擬碼將類似於:
- A在AD1中創建一個新的 appdomain。稱之為AD2。
- A在****AD2上呼叫 CreateInstanceAndUnwrap 。 B現在存在於AD2中,B (代理)存在於AD1中。
- A創建X的一個實例。
- A將X交給B(代理)
- 在AD2中,B現在有一個**X (代理)**實例(X是MBRO)
- 在AD1中,A向****X.MyEvent註冊了一個事件處理程序
- 在AD2中,B呼叫X (proxy) .FireEvent()
- 在AD1中,FireEvent在X上執行,這會觸發MyEvent
- A 的FireEvent 事件處理程序執行。
為了讓B在****AD1中觸發事件,它不僅必須具有方法,而且還必須具有觸發該方法的實例。這就是我們必須將X的代理髮送到AD2的原因。這也是為什麼跨域事件需要跨域邊界編組事件處理程序的原因! 事件只是方法執行的精美包裝。為此,您不僅需要方法,還需要執行它的實例。
經驗法則必須是,如果您希望跨應用程序域邊界處理事件,這兩種類型(一種公開事件和一種處理事件)都必須擴展 MarshalByRefObject。