c# .net remoting appdomain object-lifetime

c# - Tiempo de vida de AppDomain y MarshalByRefObject: cómo evitar RemotingException?



.net object-lifetime (8)

Cuando se pasa un objeto MarshalByRef de un AppDomain (1) a otro (2), si espera 6 minutos antes de llamar a un método en el segundo AppDomain (2) obtendrá una RemotingException:

System.Runtime.Remoting.RemotingException: el objeto [...] ha sido desconectado o no existe en el servidor.

Alguna documentación sobre este isse:

Corrígeme si estoy equivocado: si InitializeLifetimeService devuelve null, el objeto solo se puede recopilar en AppDomain 1 cuando AppDomain 2 está descargado, incluso si se recopiló el proxy.

¿Hay alguna forma de desactivar el tiempo de vida útil y mantener el proxy (en AppDomain 2) y el objeto (en AppDomain1) activo hasta que se finalice el proxy? Tal vez con ISponsor ...?


Creé una clase que se desconecta en la destrucción.

public class MarshalByRefObjectPermanent : MarshalByRefObject { public override object InitializeLifetimeService() { return null; } ~MarshalByRefObjectPermanent() { RemotingServices.Disconnect(this); } }


Finalmente encontré la manera de hacer instancias activadas por el cliente, pero involucra el código administrado en Finalizer :( Especialicé mi clase para la comunicación CrossAppDomain pero puede modificarla e intentar en otros remotos. Avíseme si encuentra algún error.

Las dos clases siguientes deben estar en un conjunto cargado en todos los dominios de aplicación implicados.

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

Y ahora CrossAppDomainObject, su objeto remoto debe heredar de esta clase en lugar de 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); } }


Lamentablemente, esta solución es incorrecta cuando AppDomains se utiliza con fines de complemento (el ensamblaje del complemento no se debe cargar en su dominio de aplicación principal).

La llamada GetRealObject () en su constructor y destructor resulta en la obtención del tipo real del objeto remoto, lo que lleva a tratar de cargar el ensamblaje del objeto remoto en el actual AppDomain. Esto puede ocasionar una excepción (si el ensamblaje no se puede cargar) o el efecto no deseado de que ha cargado un ensamblaje extraño que no puede descargar más adelante.

Una mejor solución puede ser si registra sus objetos remotos en su AppDomain principal con el método ClientSponsor.Register () (no estático, entonces debe crear una instancia de patrocinador del cliente). De forma predeterminada, renovará sus proxies remotos cada 2 minutos, lo cual es suficiente si sus objetos tienen una vida útil predeterminada de 5 minutos.


Podría probar un objeto ISponsor singleton serializable implementando IObjectReference. La implementación de GetRealObject (de IObjectReference debe devolver MySponsor.Instance cuando context.State es CrossAppDomain, de lo contrario se devolverá. MySponsor.Instance es un singleton autoinicializado, sincronizado (MethodImplOptions.Synchronized). La implementación de Renovación (desde ISponsor) debería verificar un estático MySponsor.IsFlaggedForUnload y return TimeSpan.Zero cuando está marcado para descargar / AppDomain.Current.IsFinalizingForUnload () o devolver LifetimeServices.RenewOnCallTime en caso contrario.

Para adjuntarlo, simplemente obtenga un ILease and Register (MySponsor.Instance), que se transformará en el MySponsor.Instance establecido dentro del AppDomain debido a la implementación de GetRealObject.

Para detener el patrocinio, vuelva a obtener ILease y Anular el registro (MySponsor.Instance), luego configure MySponsor.IsFlaggedForUnload a través de una devolución de llamada de AppDomain (myPluginAppDomain.DoCallback (MySponsor.FlagForUnload)).

Esto debería mantener su objeto vivo en el otro AppDomain hasta que la llamada anular el registro, la llamada FlagForUnload o AppDomain se descarguen.


Recientemente me encontré con esta excepción también. En este momento, mi solución es simplemente descargar AppDomain y luego volver a cargar AppDomain después de un largo intervalo. Afortunadamente, esta solución temporal funciona para mi caso. Ojalá haya una forma más elegante de lidiar con esto.


Si desea volver a crear el objeto remoto después de que ha sido recogido basura sin tener que crear una clase ISponsor ni darle una vida útil infinita, puede llamar a una función ficticia del objeto remoto mientras captura la 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); } }



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

He probado este y está funcionando bien, por supuesto, uno tiene que saber que el proxy vive para siempre, hasta que lo haga por sí mismo. Pero en mi caso, al usar una Plugin-Factory conectada a mi aplicación principal, no hay pérdida de memoria o algo así. Solo me aseguré de que estoy implementando IDisposable y está funcionando bien (puedo decirlo, porque mi dll cargado (en la fábrica) puede sobrescribirse una vez que la fábrica está dispuesta correctamente)

Editar: si sus eventos de burbujeo a través de los dominios, agregue esta línea de código a la clase que también crea el proxy, de lo contrario, su burbujeo también se lanzará;)