I'm having the problem described in this message board post.
I have an object that is hosted in its own AppDomain.
public class MyObject : MarshalByRefObject
{
public event EventHandler TheEvent;
...
...
}
I'd like to add a handler to that event. The handler will run in a different AppDomain. My understanding is this is all good, events get delivered across that boundary magically, with .NET Remoting.
But, when I do this:
// instance is an instance of an object that runs in a separate AppDomain
instance.TheEvent += this.Handler ;
...it compiles fine but fails at runtime with:
System.Runtime.Remoting.RemotingException:
Remoting cannot find field 'TheEvent' on type 'MyObject'.
Why?
EDIT: source code of working app that demonstrates the problem:
// 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;
}
}
To subscribe to events by using the Visual Studio IDEOn top of the Properties window, click the Events icon. Double-click the event that you want to create, for example the Load event. Visual C# creates an empty event handler method and adds it to your code. Alternatively you can add the code manually in Code view.
+= subscribes to an event. The delegate or method on the right-hand side of the += will be added to an internal list that the event keeps track of, and when the owning class fires that event, all the delegates in the list will be called.
In programming, an event handler is a callback routine that operates asynchronously once an event takes place. It dictates the action that follows the event. The programmer writes a code for this action to take place. An event is an action that takes place when a user interacts with a program.
Call an event handler using AddHandler Make sure the event is declared with an Event statement. Execute an AddHandler statement to dynamically connect the event-handling Sub procedure with the event.
The reason that your code example fails is that the event declaration and the code that subscribes to it is in the same class.
In this case, the compiler "optimizes" the code by making the code that subscribes to the event access the underlying field directly.
Basically, instead of doing this (as any code outside of the class will have to):
o.add_Event(delegateInstance);
it does this:
o.EventField = (DelegateType)Delegate.Combine(o.EventField, delegateInstance);
so, the question I have for you is this: Does your real example have the same layout of code? Is the code that subscribes to the event in the same class that declares the event?
If yes, then the next question is: Does it have to be there, or should it really be moved out of it? By moving the code out of the class, you make the compiler use the add
and ? remove
special methods that are added to your object.
The other way, if you cannot or won't move the code, would be to take over responsibility for adding and removing delegates to your event:
private EventHandler _TimerExpired;
public event EventHandler TimerExpired
{
add
{
_TimerExpired += value;
}
remove
{
_TimerExpired -= value;
}
}
This forces the compiler to call the add and remove even from code inside the same class.
Events work fine in remoting, but there are some complications, and I'm guessing you're running into one of them.
The main issue is that, for a client to subscribe to a remoted server object's event, the framework needs to have type information for both the client and the server available on both ends. Without this, you can get some remoting exceptions similar to what you're seeing.
There are ways around this, including using the observer pattern manually (vs. using an event directly), or providing a base class or interface that's available on both sides of the wire.
I recommend reading this CodeProject article. It walks through using events with remoting, and has a good description of this issue, in the section titled "Raising events from remote objects".
Basically, the main thing is to make sure your handlers follow specific guidelines, including being concrete, non-virtual, etc. The article walks through specifics, and provides working examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With