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

Срок службы AppDomain и MarshalByRefObject: как избежать RemotingException?

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

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

Некоторая документация об этом isse:

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

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

4b9b3361

Ответ 2

Наконец-то я нашел способ активировать клиентские экземпляры, но он включает в себя управляемый код в 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 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);
    }
  }

Ответ 3

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

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

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

Ответ 4

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

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

    ~MarshalByRefObjectPermanent()
    {
        RemotingServices.Disconnect(this);
    }
}

Ответ 5

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

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

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

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

Ответ 6

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

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

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

Ответ 7

Если вы захотите заново создать удаленный объект после сбора мусора без создания класса 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);
    }
}

Ответ 8

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

Подход 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;
    }
}

Тем не менее, как пользователь 266648 указывает в другом ответе

это решение не сработало бы, если бы такой объект создавался каждый раз, когда клиент подключается сам, потому что он никогда не будет 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();
      }
   }
}

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

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

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

Ответ 9

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