我遇到了 this message board post 中描述的问题.
我有一个托管在自己的 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;
}
}
最佳答案
您的代码示例失败的原因是事件声明和订阅它的代码在同一个类中。
在这种情况下,编译器通过使订阅事件的代码直接访问底层字段来“优化”代码。
基本上,不要这样做(因为类之外的任何代码都必须这样做):
o.add_Event(delegateInstance);
它这样做:
o.EventField = (DelegateType)Delegate.Combine(o.EventField, delegateInstance);
所以,我要问你的问题是:你的真实例子有相同的代码布局吗?订阅事件的代码是否在声明事件的同一类中?
如果是,那么下一个问题是:它必须在那里,还是真的应该被移出?通过将代码移出类,您可以使编译器使用
add
和 ? remove
添加到您的对象的特殊方法。另一种方式,如果您不能或不会移动代码,则将负责为您的事件添加和删除代表:
private EventHandler _TimerExpired;
public event EventHandler TimerExpired
{
add
{
_TimerExpired += value;
}
remove
{
_TimerExpired -= value;
}
}
这迫使编译器甚至从同一类内的代码中调用添加和删除。
关于.net - 如何跨 AppDomains 订阅事件(object.Event += handler;),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1390564/