Время жизни AppDomain и MarshalByRefObject: как избежать исключения RemotingException? - PullRequest
55 голосов
/ 09 марта 2010

Когда объект MarshalByRef передается из AppDomain (1) другому (2), если вы будете ждать 6 минут, прежде чем вызывать метод для него во втором AppDomain (2), вы получите RemotingException:

System.Runtime.Remoting.RemotingException: Объект [...] был отключен или не существует на сервере.

Некоторая документация по этому вопросу:

Поправьте меня, если я ошибаюсь: если InitializeLifetimeService возвращает ноль, объект может быть собран только в AppDomain 1, когда AppDomain 2 выгружен, даже если был собран прокси?

Есть ли способ отключить время жизни и сохранить прокси (в AppDomain 2) и объект (в AppDomain1) живыми, пока прокси не будет завершен? Может быть, с ISponsor ...?

Ответы [ 9 ]

41 голосов
/ 24 мая 2011

см. Ответ здесь:

http://social.msdn.microsoft.com/Forums/en-US/netfxremoting/thread/3ab17b40-546f-4373-8c08-f0f072d818c9/

, что в основном говорит:

[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
  return null;
}
13 голосов
/ 11 марта 2010

Наконец-то я нашел способ сделать клиентские экземпляры активированными, но он включает в себя управляемый код в Finalizer :( Я специализировал свой класс для связи через CrossAppDomain, но вы можете изменить его и попробовать другие удаленные взаимодействия. Дайте мне знать, если найдете ошибку.

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

  /// <summary>
  /// Stores all relevant information required to generate a proxy in order to communicate with a remote object.
  /// Disconnects the remote object (server) when finalized on local host (client).
  /// </summary>
  [Serializable]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public sealed class CrossAppDomainObjRef : ObjRef
  {
    /// <summary>
    /// Initializes a new instance of the CrossAppDomainObjRef class to
    /// reference a specified CrossAppDomainObject of a specified System.Type.
    /// </summary>
    /// <param name="instance">The object that the new System.Runtime.Remoting.ObjRef instance will reference.</param>
    /// <param name="requestedType"></param>
    public CrossAppDomainObjRef(CrossAppDomainObject instance, Type requestedType)
      : base(instance, requestedType)
    {
      //Proxy created locally (not remoted), the finalizer is meaningless.
      GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Initializes a new instance of the System.Runtime.Remoting.ObjRef class from
    /// serialized data.
    /// </summary>
    /// <param name="info">The object that holds the serialized object data.</param>
    /// <param name="context">The contextual information about the source or destination of the exception.</param>
    private CrossAppDomainObjRef(SerializationInfo info, StreamingContext context)
      : base(info, context)
    {
      Debug.Assert(context.State == StreamingContextStates.CrossAppDomain);
      Debug.Assert(IsFromThisProcess());
      Debug.Assert(IsFromThisAppDomain() == false);
      //Increment ref counter
      CrossAppDomainObject remoteObject = (CrossAppDomainObject)GetRealObject(new StreamingContext(StreamingContextStates.CrossAppDomain));
      remoteObject.AppDomainConnect();
    }

    /// <summary>
    /// Disconnects the remote object.
    /// </summary>
    ~CrossAppDomainObjRef()
    {
      Debug.Assert(IsFromThisProcess());
      Debug.Assert(IsFromThisAppDomain() == false);
      //Decrement ref counter
      CrossAppDomainObject remoteObject = (CrossAppDomainObject)GetRealObject(new StreamingContext(StreamingContextStates.CrossAppDomain));
      remoteObject.AppDomainDisconnect();
    }

    /// <summary>
    /// Populates a specified System.Runtime.Serialization.SerializationInfo with
    /// the data needed to serialize the current System.Runtime.Remoting.ObjRef instance.
    /// </summary>
    /// <param name="info">The System.Runtime.Serialization.SerializationInfo to populate with data.</param>
    /// <param name="context">The contextual information about the source or destination of the serialization.</param>
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      Debug.Assert(context.State == StreamingContextStates.CrossAppDomain);
      base.GetObjectData(info, context);
      info.SetType(typeof(CrossAppDomainObjRef));
    }
  }

А теперь CrossAppDomainObject, ваш удаленный объект должен наследовать от этого класса вместо MarshalByRefObject.

  /// <summary>
  /// Enables access to objects across application domain boundaries.
  /// Contrary to MarshalByRefObject, the lifetime is managed by the client.
  /// </summary>
  public abstract class CrossAppDomainObject : MarshalByRefObject
  {
    /// <summary>
    /// Count of remote references to this object.
    /// </summary>
    [NonSerialized]
    private int refCount;

    /// <summary>
    /// Creates an object that contains all the relevant information required to
    /// generate a proxy used to communicate with a remote object.
    /// </summary>
    /// <param name="requestedType">The System.Type of the object that the new System.Runtime.Remoting.ObjRef will reference.</param>
    /// <returns>Information required to generate a proxy.</returns>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed override ObjRef CreateObjRef(Type requestedType)
    {
      CrossAppDomainObjRef objRef = new CrossAppDomainObjRef(this, requestedType);
      return objRef;
    }

    /// <summary>
    /// Disables LifeTime service : object has an infinite life time until it's Disconnected.
    /// </summary>
    /// <returns>null.</returns>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed override object InitializeLifetimeService()
    {
      return null;
    }

    /// <summary>
    /// Connect a proxy to the object.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public void AppDomainConnect()
    {
      int value = Interlocked.Increment(ref refCount);
      Debug.Assert(value > 0);
    }

    /// <summary>
    /// Disconnects a proxy from the object.
    /// When all proxy are disconnected, the object is disconnected from RemotingServices.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public void AppDomainDisconnect()
    {
      Debug.Assert(refCount > 0);
      if (Interlocked.Decrement(ref refCount) == 0)
        RemotingServices.Disconnect(this);
    }
  }
6 голосов
/ 16 апреля 2010

К сожалению, это решение неверно, когда домены приложений используются в целях плагина (сборка плагина не должна загружаться в ваш основной домен приложения).

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

Лучшее решение может быть, если вы зарегистрируете свои удаленные объекты в основном домене приложения с помощью метода ClientSponsor.Register () (не статично, поэтому вы должны создать экземпляр спонсора клиента). По умолчанию он обновляет удаленные прокси-серверы каждые 2 минуты, что достаточно, если срок действия ваших объектов по умолчанию составляет 5 минут.

1 голос
/ 27 января 2019

Здесь есть два возможных решения.

Подход Singleton: переопределить InitializeLifetimeService

Как Саша Гольдштейн указывает на сообщение в блоге , на которое ссылается оригинальный постер, если у вашего объекта Marshaled есть семантика Singleton, вы можете переопределить InitializeLifetimeService:

class MyMarshaledObject : MarshalByRefObject
{
    public bool DoSomethingRemote() 
    {
      // ... execute some code remotely ...
      return true; 
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
    public override object InitializeLifetimeService()
    {
      return null;
    }
}

Однако, как user266748 указывает в другой ответ

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

Подход на основе классов: использование ClientSponsor

Более общим решением является использование ClientSponsor для продления срока службы удаленного объекта, активируемого классом. В связанной статье MSDN есть полезный стартовый пример, которому вы можете следовать:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Lifetime;
namespace RemotingSamples
{

   class HelloClient
   {
       static void Main()
      {
         // Register a channel.
         TcpChannel myChannel = new TcpChannel ();
         ChannelServices.RegisterChannel(myChannel);
         RemotingConfiguration.RegisterActivatedClientType(
                                typeof(HelloService),"tcp://localhost:8085/");

         // Get the remote object.
         HelloService myService = new HelloService();

         // Get a sponsor for renewal of time.
         ClientSponsor mySponsor = new ClientSponsor();

         // Register the service with sponsor.
         mySponsor.Register(myService);

         // Set renewaltime.
         mySponsor.RenewalTime = TimeSpan.FromMinutes(2);

         // Renew the lease.
         ILease myLease = (ILease)mySponsor.InitializeLifetimeService();
         TimeSpan myTime = mySponsor.Renewal(myLease);
         Console.WriteLine("Renewed time in minutes is " + myTime.Minutes.ToString());

         // Call the remote method.
         Console.WriteLine(myService.HelloMethod("World"));

         // Unregister the channel.
         mySponsor.Unregister(myService);
         mySponsor.Close();
      }
   }
}

Ничего не стоит, как управление жизненным циклом работает в API удаленного взаимодействия, который довольно хорошо описан здесь, в MSDN . Я процитировал ту часть, которая мне показалась наиболее полезной:

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

В каждом домене приложения есть менеджер по аренде, который отвечает за для контроля аренды в своем домене. Все договоры аренды рассматриваются периодически для истекших сроков аренды. Если срок аренды истек, один или больше спонсоров аренды призваны и получили возможность возобновить аренду. Если ни один из спонсоров не решит продлить договор аренды, менеджер по аренде снимает аренду, и объект может быть забран сборщик мусора. Менеджер по аренде ведет список аренды с аренды отсортированы по оставшемуся времени аренды. Аренда с самым коротким оставшееся время хранится в верхней части списка. Remoting Пожизненная служба связывает аренду с каждой службой и удаляет обслуживание, когда срок его аренды истекает.

1 голос
/ 24 февраля 2016

Я создал класс, который отключается на уничтожение.

public class MarshalByRefObjectPermanent : MarshalByRefObject
{
    public override object InitializeLifetimeService()
    {
        return null;
    }

    ~MarshalByRefObjectPermanent()
    {
        RemotingServices.Disconnect(this);
    }
}
0 голосов
/ 09 октября 2016

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

public static class MyClientClass
{
    private static MarshalByRefObject remoteClass;

    static MyClientClass()
    {
        CreateRemoteInstance();
    }

    // ...

    public static void DoStuff()
    {
        // Before doing stuff, check if the remote object is still reachable
        try {
            remoteClass.GetLifetimeService();
        }
        catch(RemotingException) {
            CreateRemoteInstance(); // Re-create remote instance
        }

        // Now we are sure the remote class is reachable
        // Do actual stuff ...
    }

    private static void CreateRemoteInstance()
    {
        remoteClass = (MarshalByRefObject)AppDomain.CurrentAppDomain.CreateInstanceFromAndUnwrap(remoteClassPath, typeof(MarshalByRefObject).FullName);
    }
}
0 голосов
/ 13 октября 2015
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
  return null;
}

Я проверил этот и он работает нормально, конечно, нужно знать, что прокси живет вечно, пока вы не сделаете GC-ing для себя. Но в моем случае, с использованием Plugin-Factory, подключенного к моему основному приложению, нет утечки памяти или чего-то в этом роде. Я только что убедился, что я реализую IDisposable, и он работает нормально (я могу сказать, потому что мои загруженные библиотеки DLL (на заводе) могут быть перезаписаны, как только фабрика будет расположена правильно)

Редактировать: если ваши события всплывают через домены, добавьте эту строку кода в класс, также создавая прокси, иначе ваше всплывающее окно тоже выдаст;)

0 голосов
/ 10 июня 2014

Вы можете попробовать сериализуемый одноэлементный объект ISponsor, реализующий IObjectReference. Реализация GetRealObject (из IObjectReference должна возвращать MySponsor.Instance, когда context.State равен CrossAppDomain, в противном случае возвращать себя. MySponsor.Instance - самоинициализируемый, синхронизированный (MethodImplOptions.Synchronized), singleton. Реализация Renewal (из ISponsor) должна проверить static MySponsor.IsFlaggedForUnload и возврат TimeSpan.Zero, если он помечен как unload / AppDomain.Current.IsFinalizingForUnload () или в противном случае возвращает LifetimeServices.RenewOnCallTime.

Чтобы присоединить его, просто получите ILease and Register (MySponsor.Instance), который будет преобразован в набор MySponsor.Instance в AppDomain благодаря реализации GetRealObject.

Чтобы прекратить спонсорство, повторно получите ILease и Unregister (MySponsor.Instance), затем установите MySponsor.IsFlaggedForUnload через перекрестный обратный вызов AppDomain (myPluginAppDomain.DoCallback (MySponsor.FlagForUnload)).

Это должно поддерживать ваш объект в другом AppDomain до тех пор, пока не будет отменен вызов отмены регистрации, вызов FlagForUnload или AppDomain.

0 голосов
/ 24 января 2011

Я недавно столкнулся с этим исключением также. Прямо сейчас мое решение состоит в том, чтобы просто выгрузить AppDomain, а затем перезагрузить AppDomain после долгого интервала. К счастью, это временное решение работает для моего случая. Хотелось бы, чтобы был более элегантный способ справиться с этим.

...