2009-09-07 7 views
12

Sto riscontrando il problema descritto in this message board post.Come posso iscrivermi a un evento su AppDomains (object.Event + = handler;)

Ho un oggetto ospitato nel proprio AppDomain.

public class MyObject : MarshalByRefObject 
{ 
    public event EventHandler TheEvent; 
    ... 
    ... 
} 

Mi piacerebbe aggiungere un gestore a quell'evento. Il gestore verrà eseguito in un diverso AppDomain. La mia comprensione è che tutto va bene, gli eventi vengono consegnati magicamente attraverso quel confine, con .NET Remoting.

Ma, quando faccio questo:

// instance is an instance of an object that runs in a separate AppDomain 
instance.TheEvent += this.Handler ; 

... si compila bene, ma non riesce a runtime con:

System.Runtime.Remoting.RemotingException: 
    Remoting cannot find field 'TheEvent' on type 'MyObject'. 

Perché?

EDIT: codice sorgente di un'applicazione di lavoro che illustra il problema:

// EventAcrossAppDomain.cs 
// ------------------------------------------------------------------ 
// 
// demonstrate an exception that occurs when trying to use events across AppDomains. 
// 
// The exception is: 
// System.Runtime.Remoting.RemotingException: 
//  Remoting cannot find field 'TimerExpired' on type 'Cheeso.Tests.EventAcrossAppDomain.MyObject'. 
// 
// compile with: 
//  c:\.net3.5\csc.exe /t:exe /debug:full /out:EventAcrossAppDomain.exe EventAcrossAppDomain.cs 
// 

using System; 
using System.Threading; 
using System.Reflection; 

namespace Cheeso.Tests.EventAcrossAppDomain 
{ 
    public class MyObject : MarshalByRefObject 
    { 
     public event EventHandler TimerExpired; 
     public EventHandler TimerExpired2; 

     public MyObject() { } 

     public void Go(int seconds) 
     { 
      _timeToSleep = seconds; 
      ThreadPool.QueueUserWorkItem(Delay); 
     } 

     private void Delay(Object stateInfo) 
     { 
      System.Threading.Thread.Sleep(_timeToSleep * 1000); 
      OnExpiration(); 
     } 

     private void OnExpiration() 
     { 
      Console.WriteLine("OnExpiration (threadid={0})", 
           Thread.CurrentThread.ManagedThreadId); 
      if (TimerExpired!=null) 
       TimerExpired(this, EventArgs.Empty); 

      if (TimerExpired2!=null) 
       TimerExpired2(this, EventArgs.Empty); 
     } 

     private void ChildObjectTimerExpired(Object source, System.EventArgs e) 
     { 
      Console.WriteLine("ChildObjectTimerExpired (threadid={0})", 
           Thread.CurrentThread.ManagedThreadId); 
      _foreignObjectTimerExpired.Set(); 
     } 

     public void Run(bool demonstrateProblem) 
     { 
      try 
      { 
       Console.WriteLine("\nRun()...({0})", 
            (demonstrateProblem) 
            ? "will demonstrate the problem" 
            : "will avoid the problem"); 

       int delaySeconds = 4; 
       AppDomain appDomain = AppDomain.CreateDomain("appDomain2"); 
       string exeAssembly = Assembly.GetEntryAssembly().FullName; 

       MyObject o = (MyObject) appDomain.CreateInstanceAndUnwrap(exeAssembly, 
                      typeof(MyObject).FullName); 

       if (demonstrateProblem) 
       { 
        // the exception occurs HERE 
        o.TimerExpired += ChildObjectTimerExpired; 
       } 
       else 
       { 
        // workaround: don't use an event 
        o.TimerExpired2 = ChildObjectTimerExpired; 
       } 

       _foreignObjectTimerExpired = new ManualResetEvent(false); 

       o.Go(delaySeconds); 

       Console.WriteLine("Run(): hosted object will Wait {0} seconds...(threadid={1})", 
            delaySeconds, 
            Thread.CurrentThread.ManagedThreadId); 

       _foreignObjectTimerExpired.WaitOne(); 

       Console.WriteLine("Run(): Done."); 

      } 
      catch (System.Exception exc1) 
      { 
       Console.WriteLine("In Run(),\n{0}", exc1.ToString()); 
      } 
     } 



     public static void Main(string[] args) 
     { 
      try 
      { 
       var o = new MyObject(); 
       o.Run(true); 
       o.Run(false); 
      } 
      catch (System.Exception exc1) 
      { 
       Console.WriteLine("In Main(),\n{0}", exc1.ToString()); 
      } 
     } 

     // private fields 
     private int _timeToSleep; 
     private ManualResetEvent _foreignObjectTimerExpired; 

    } 
} 

risposta

13

Il motivo per cui l'esempio di codice non riesce è che la dichiarazione di evento e il codice che lo sottoscrive si trovano nella stessa classe.

In questo caso, il compilatore "ottimizza" il codice rendendo il codice che sottoscrive l'evento accede direttamente al campo sottostante.

In sostanza, invece di fare questo (come qualsiasi codice esterno della classe dovrà):

o.add_Event(delegateInstance); 

lo fa in questo modo:

o.EventField = (DelegateType)Delegate.Combine(o.EventField, delegateInstance); 

così, la domanda che ho per voi è questo: il tuo esempio reale ha lo stesso layout di codice? Il codice che si abbona all'evento nella stessa classe che dichiara l'evento?

In caso affermativo, la domanda successiva è: deve essere presente o deve essere spostata al di fuori di esso?Spostando il codice dalla classe, il compilatore usa il add e? remove metodi speciali che vengono aggiunti al tuo oggetto.

L'altro modo, se non può o non spostare il codice, sarebbe quella di prendere la responsabilità per l'aggiunta e la rimozione di delegati per il vostro evento:

private EventHandler _TimerExpired; 
public event EventHandler TimerExpired 
{ 
    add 
    { 
     _TimerExpired += value; 
    } 

    remove 
    { 
     _TimerExpired -= value; 
    } 
} 

Questo costringe il compilatore di chiamare l'add e rimuovere anche dal codice all'interno della stessa classe.

+0

Eccellente! ~ Grazie per la spiegazione. – Cheeso

5

Eventi funzionano bene in servizi remoti, ma ci sono alcune complicazioni, e sto cercando di indovinare si sta eseguendo in uno di loro .

Il problema principale è che, per un client per la sottoscrizione di un evento di un oggetto server remoto, il framework deve disporre di informazioni sul tipo sia per il client che per il server disponibili su entrambe le estremità. Senza questo, è possibile ottenere alcune eccezioni remote simili a ciò che si sta vedendo.

Ci sono alcuni modi per aggirare questo problema, incluso l'utilizzo manuale del modello di osservatore (rispetto all'utilizzo diretto di un evento) o la fornitura di una classe base o di un'interfaccia disponibile su entrambi i lati del filo.

Consiglio di leggere questo CodeProject article. Passa attraverso gli eventi con il servizio remoto e ha una buona descrizione di questo problema nella sezione intitolata "Aumentare gli eventi dagli oggetti remoti".

Fondamentalmente, la cosa principale è assicurarsi che i gestori rispettino linee guida specifiche, tra cui essere concreti, non virtuali, ecc. L'articolo esamina le specifiche e fornisce esempi di lavoro.

+0

ok, ma si applica il problema "digita informazioni su entrambe le estremità" se il tipo con l'evento è definito nello stesso assembly del tipo con il gestore e se i due AppDomain sono in esecuzione nello stesso processo sullo stesso macchina? È un host personalizzato ASPNET. Il programma si avvia e chiama CreateApplicationHost(). – Cheeso

+0

L'ho provato anche usando lo stesso Type sia come editore sia come sottoscrittore dell'evento. Un'istanza del tipo è l'editore, un'altra istanza del tipo in un AppDomain separato è il sottoscrittore. Stessi risultati Quindi sembra che "le informazioni sul tipo non sono disponibili su entrambe le estremità del filo" non è il problema che sto vedendo. – Cheeso

+0

Dovrebbe funzionare se sono lo stesso tipo di oggetto. Ti stai iscrivendo a un metodo pubblico, non virtuale (es: il gestore)? Se il metodo è virutale, spesso causa strani problemi. –

Problemi correlati