2009-08-14 11 views
8

Oto podstawowy sens mojego problemu:NET: Problem z podnoszeniem i obsługi zdarzeń za pomocą AppDomains

  1. Moje główne klasy Okno instancję klasy A.
  2. Klasa A Klasa B instancję w wtórnego AppDomain.
  3. Klasa B podnosi wydarzenie, a klasa A skutecznie radzi sobie z wydarzeniem.
  4. Klasa A podnosi własne wydarzenie.

Problem: W kroku 4, gdy klasa A podnosi własne zdarzenie z metody obsługi zdarzeń, które złowionych zdarzenie Klasa B, tym zdarzenie jest wywoływane; Jednak program obsługi subskrypcji w klasie okna nigdy nie jest wywoływany.

Nie ma żadnych wyjątków. Jeśli usuniemy pomocniczą domenę aplikacji, zdarzenie zostanie obsłużone bez problemu.

Czy ktoś wie, dlaczego to nie działa? Czy istnieje inny sposób, aby to działało bez użycia wywołania zwrotnego?

Myślę, jeśli cokolwiek, problem będzie występować w kroku 3 zamiast kroku 4.

Oto próbka prawdziwy kod do zilustrowania problemu:

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 

Odpowiedz

5

W mojej pierwszej próbie na rozwiązywanie w tym wydaniu usunięto dziedziczenie klasy B z oznaczeniem MarshalByRefObject i oznaczono je jako możliwe do serializacji. W rezultacie obiekt został zebrany według wartości i właśnie dostałem kopię klasy C, która jest wykonywana w hoście AppDomain. Nie tego chciałem.

Rzeczywisty rozwiązanie, to stwierdzono, było to, że Klasa B (DangerousProgram w przykładzie) powinny też dziedziczyć MarshalByRefObject tak że połączenie wykorzystuje tylne także proxy przejście nici z powrotem na domyślny AppDomain.

Przy okazji, here's a great article Znalazłem Eric Lippert, który tłumaczy marszałka przez ref vs. marszałek według wartości w bardzo sprytny sposób.

31

Magia zdarzeń .NET ukrywa fakt, że kiedy subskrybujesz wydarzenie w instancji B przez instancję A, A zostaje wysłana do domeny app B. Jeśli A nie jest MarshalByRef, wówczas wysyłana jest kopia wartości A. Teraz masz dwa oddzielne wystąpienia A, dlatego doświadczyłeś nieoczekiwanych zachowań.

Jeśli ktoś ma trudności ze zrozumieniem, jak to się dzieje, sugeruję następujące obejście, które wyjaśnia, dlaczego zdarzenia zachowują się w ten sposób.

Aby podnieść "zdarzenia" w B (w appdomainain 2) i obsłużyć je w A (w appdomainain 1) bez użycia prawdziwych zdarzeń, musimy stworzyć drugi obiekt, który tłumaczy wywołania metod (które przekraczają granice bez większego wysiłku) do wydarzeń (które nie zachowują się tak, jak można się tego spodziewać). Ta klasa, wywołaj ją jako X, zostanie utworzona w appdomaine 1, a jej proxy zostanie wysłane do appdomain 2.Oto kod:

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

pseudokod pójdzie coś takiego:

  1. w terminie AD1, tworzy nową AppDomain. Nazywają to AD2.
  2. A połączenia CreateInstanceAndUnwrap na AD2. B istnieje obecnie AD2 i B (proxy) istnieje w AD1.
  3. A tworzy instancję X.
  4. A ręce X do B (proxy)
  5. W AD2, B ma teraz przypadek X (proxy) (X jest MBRO)
  6. W AD1, A rejestruje obsługi zdarzeń z X.MyEvent
  7. W AD2, B wywołuje X (proxy) .FireEvent()
  8. W AD1, FireEvent działający na X, który odpala MyEvent
  9. A's Program obsługi zdarzeń dla FireEvent.

Aby B ognia zdarzenie z powrotem w AD1, to nie tylko musi mieć metody, ale także wystąpienie do pożaru, który na metodę. Dlatego musimy wysłać proxy o adresie: X do AD2. To jest również dlaczego zdarzenia między domenami wymagają, aby program obsługi zdarzeń był przekierowywany przez granicę domeny! Zdarzenie jest tylko fantazyjnym opakowaniem wokół wykonywania metody. Aby to zrobić, potrzebujesz nie tylko metody, ale także instancji, aby ją uruchomić.

Reguła musi polegać na tym, że jeśli chcesz obsługiwać zdarzenia na granicy domeny aplikacji, oba typy - wystawiający zdarzenie i obsługujący go - muszą rozszerzać MarshalByRefObject.

+0

Podoba mi się Twoja sugestia. Twoje ostatnie oświadczenie jest doskonałym podsumowaniem. Dzięki! –

+0

Świetne rozwiązanie! Alternatywnie możemy zezwolić ** AD2 ** na posiadanie "RealProxy" ** X ** i subskrybowanie ** X.MyEvent ** od ** AD1 **. Wykonanie tego będzie wymagało, aby funkcja obsługi zdarzeń ** A ** została zebrana, co zostało przekazane do ** AD2 **. Kiedy zdarzenie zostanie uruchomione, ** AD2 ** używa teraz 'TransparentProxy' programu obsługi zdarzenia. To jest po prostu odwrotność rozwiązania, więc może być przydatne w zależności od tego, kto (która domena aplikacji) musi być właścicielem "RealProxy" zdarzenia. –