.net - 如何跨 AppDomains 订阅事件(object.Event += handler;)

标签 .net events remoting

我遇到了 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/

相关文章:

c# - "arbitrary"分隔符之间的 RegEx 替换

.net - 你如何组织你的命名空间?

c# - 如何在 C# 6 中使用带字符串插值的转义字符?

actionscript-3 - 实例之间共享的 AS3 中央事件调度程序(非静态)

C# PowerShell 远程处理问题 : method or operation not implemented

c# - 使用 C# TcpChannel 服务器的 Java RMI 客户端

.net - 后台线程打印问题

javascript - 如何针对事件流定位 GUI 的特定对象?

javascript - AngularJS:如果只按下输入键

ios - 远程构建 iOS 应用程序