Подтвердить что ты не робот

Как я могу подписаться на событие через AppDomains (object.Event + = обработчик;)

У меня возникла проблема, описанная в этой доске сообщений.

У меня есть объект, размещенный в его собственном AppDomain.

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

Я хочу добавить обработчик к этому событию. Обработчик будет работать в другом приложении. Я понимаю, что все это хорошо, события доставляются через эту границу магически, с .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'.

Почему?

EDIT: исходный код рабочего приложения, демонстрирующий проблему:

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

    }
}
4b9b3361

Ответ 1

Причина, по которой ваш пример кода терпит неудачу, заключается в том, что объявление события и код, подписавшийся на него, находятся в одном классе.

В этом случае компилятор "оптимизирует" код, заставляя код, который подписывается на событие, напрямую обращаться к базовому полю.

В принципе, вместо этого (поскольку любой код вне класса должен будет):

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

Это заставляет компилятор вызывать добавление и удаление даже из кода внутри того же класса.

Ответ 2

События прекрасно работают при удалении, но есть некоторые сложности, и я предполагаю, что вы работаете в одном из них.

Основная проблема заключается в том, что для того, чтобы клиент подписался на событие удаленного объекта сервера, инфраструктура должна иметь информацию о типе как для клиента, так и для сервера, доступного на обоих концах. Без этого вы можете получить некоторые удаленные исключения, похожие на то, что вы видите.

Есть способы обойти это, включая использование шаблона наблюдателя вручную (напрямую или напрямую с помощью события) или предоставление базового класса или интерфейса, доступного по обеим сторонам провода.

Я рекомендую прочитать эту статью CodeProject. Он просматривает события с удалением и имеет хорошее описание этой проблемы в разделе "Повышение событий от удаленных объектов".

В основном, главное - убедиться, что ваши обработчики следуют конкретным рекомендациям, в том числе конкретным, не виртуальным и т.д. В статье рассматриваются специфические особенности и приводятся рабочие примеры.