2009-09-07 5 views
12

this message board post에 설명 된 문제가 있습니다.AppDomains (object.Event + = handler;)에서 이벤트를 구독하는 방법

자체 AppDomain에서 호스팅되는 개체가 있습니다.

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

해당 이벤트에 처리기를 추가하고 싶습니다. 핸들러는 다른 AppDomain에서 실행됩니다. .NET Remoting을 사용하면 마술처럼 경계를 넘어 이벤트가 전달됩니다.

, 나는이 작업을 수행 할 때

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

... 그것을 잘 컴파일하지만 함께 런타임에 실패

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

이유는 무엇입니까?

편집 : 작업 응용 프로그램의 소스 코드 문제를 보여줍니다

// 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; 

    } 
} 

답변

13

코드 예제가 실패하는 이유는 이벤트 선언과 코드를 구독하는 코드가 같은 클래스에 있기 때문입니다.

이 경우 컴파일러는 이벤트를 구독하는 코드를 기본 필드에 직접 액세스함으로써 코드를 "최적화"합니다. 대신이 일을

기본적으로, (클래스의 외부 코드가있을 것 같은) :

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

그래서, 내가 당신을 위해 가지고있는 질문은 :

o.add_Event(delegateInstance); 

그것을이 수행 이것은 : 실제 예제가 동일한 코드 레이아웃을 가지고 있습니까? 이벤트를 선언하는 동일한 클래스의 이벤트를 구독하는 코드가 있습니까?

'예'인 경우 다음 질문입니다. '실제로 있어야합니까? 아니면 실제로 이동해야합니까?코드를 클래스 밖으로 이동하면 컴파일러에서 add을 사용하고? remove 개체에 추가되는 특수 메서드입니다. 당신이 또는 코드를 이동하지 않습니다 수없는 경우

다른 방법은, 이벤트에 참가자를 추가 및 제거에 대한 책임을 가지고하는 것입니다 :

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

    remove 
    { 
     _TimerExpired -= value; 
    } 
} 

이 추가 전화 컴파일러를 강제로 동일한 클래스 내의 코드에서도 제거하십시오.

+0

우수! ~ 설명 주셔서 감사합니다. – Cheeso

5

이벤트가 원격에서 잘 작동하지만 몇 가지 합병증이있다, 나는 당신이 그들 중 하나에 실행중인 같은데요를 .

주된 문제점은 클라이언트가 원격 서버 객체의 이벤트를 구독하려면 프레임 워크가 클라이언트와 서버 모두에 대해 양쪽 유형에서 사용할 수있는 유형 정보가 있어야한다는 것입니다. 이것이 없다면, 당신은 당신이 보는 것과 유사한 원격 예외를 얻을 수 있습니다.

수동으로 관찰자 패턴을 사용하는 것 (대 이벤트 직접 사용)이나 전선 양쪽에서 사용할 수있는 기본 클래스 나 인터페이스를 제공하는 방법이 있습니다.

나는 이것을 읽는 것이 좋습니다 CodeProject article. 원격 작업과 함께 이벤트를 사용하여 진행하고 "원격 개체에서 이벤트 발생"절에서이 문제에 대해 잘 설명합니다.

기본적으로 핸들러가 구체적이고 비가 상적이라는 구체적인 지침을 따르는 것이 중요합니다.이 기사는 구체적인 내용을 다루며 작업 예제를 제공합니다.

+0

괜찮지 만 이벤트가있는 유형이 처리기가있는 유형과 동일한 어셈블리에 정의되어 있고 두 AppDomains가 동일한 프로세스 내에서 동일하게 실행되는 경우 "양 끝 유형 정보"문제가 적용됩니다 기계? ASPNET 사용자 지정 호스트입니다. 프로그램이 시작되고 CreateApplicationHost()를 호출합니다. – Cheeso

+0

나는 또한 이벤트의 게시자와 구독자와 같은 유형을 사용하여 시도했다. 유형의 한 인스턴스는 게시자이고 별도의 AppDomain에있는 다른 유형의 인스턴스는 구독자입니다. 같은 결과. 따라서 "유형 정보는 전선 양쪽에서 사용할 수 없습니다"와 같은 문제는 내가보기에는 문제가 아닙니다. – Cheeso

+0

동일한 유형의 객체 인 경우 작동해야합니다. 공개가 아닌 가상 메서드 (예 : 처리기)를 구독하고 있습니까? 메서드의 virutal, 그것은 종종 이상한 문제가 발생합니다. –

관련 문제