Durée de vie AppDomain et MarshalByRefObject: comment éviter une exception RemotingException?

Lorsqu’un object MarshalByRef est passé d’un AppDomain (1) à un autre (2), si vous attendez 6 minutes avant d’appeler une méthode dans le second AppDomain (2), vous obtiendrez une exception RemotingException:

System.Runtime.Remoting.RemotingException: object […] a été déconnecté ou n’existe pas sur le serveur.

Quelques documents à ce sujet:

  • http://blogs.microsoft.co.il/blogs/sasha/archive/2008/07/19/appdomains-and-remoting-life-time-service.aspx
  • http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx – Durée de vie de l’instance, cbrumme dit: “Nous devrions résoudre ce problème”. 🙁

Corrigez-moi si je me trompe: si InitializeLifetimeService renvoie null, l’object ne peut être collecté que dans AppDomain 1 lorsque AppDomain 2 est déchargé, même si le proxy a été collecté?

Existe-t-il un moyen de désactiver la durée de vie et de conserver le proxy (dans AppDomain 2) et l’object (dans AppDomain1) en vie jusqu’à ce que le proxy soit finalisé? Peut-être avec ISponsor …?

voir réponse ici:

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

qui dit essentiellement:

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

J’ai finalement trouvé un moyen de faire des instances activées par le client, mais il utilise du code géré dans Finalizer 🙁 Je me suis spécialisé dans la communication CrossAppDomain, mais vous pouvez le modifier et essayer d’autres utilisateurs distants.

Les deux classes suivantes doivent être dans un assembly chargé dans tous les domaines d’application impliqués.

  ///  /// 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). ///  [Serializable] [EditorBrowsable(EditorBrowsableState.Never)] public sealed class CrossAppDomainObjRef : ObjRef { ///  /// Initializes a new instance of the CrossAppDomainObjRef class to /// reference a specified CrossAppDomainObject of a specified System.Type. ///  /// The object that the new System.Runtime.Remoting.ObjRef instance will reference. ///  public CrossAppDomainObjRef(CrossAppDomainObject instance, Type requestedType) : base(instance, requestedType) { //Proxy created locally (not remoted), the finalizer is meaningless. GC.SuppressFinalize(this); } ///  /// Initializes a new instance of the System.Runtime.Remoting.ObjRef class from /// serialized data. ///  /// The object that holds the serialized object data. /// The contextual information about the source or destination of the exception. 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(); } ///  /// Disconnects the remote object. ///  ~CrossAppDomainObjRef() { Debug.Assert(IsFromThisProcess()); Debug.Assert(IsFromThisAppDomain() == false); //Decrement ref counter CrossAppDomainObject remoteObject = (CrossAppDomainObject)GetRealObject(new StreamingContext(StreamingContextStates.CrossAppDomain)); remoteObject.AppDomainDisconnect(); } ///  /// Populates a specified System.Runtime.Serialization.SerializationInfo with /// the data needed to serialize the current System.Runtime.Remoting.ObjRef instance. ///  /// The System.Runtime.Serialization.SerializationInfo to populate with data. /// The contextual information about the source or destination of the serialization. public override void GetObjectData(SerializationInfo info, StreamingContext context) { Debug.Assert(context.State == StreamingContextStates.CrossAppDomain); base.GetObjectData(info, context); info.SetType(typeof(CrossAppDomainObjRef)); } } 

Et maintenant, CrossAppDomainObject, votre object distant doit hériter de cette classe au lieu de MarshalByRefObject.

  ///  /// Enables access to objects across application domain boundaries. /// Contrary to MarshalByRefObject, the lifetime is managed by the client. ///  public abstract class CrossAppDomainObject : MarshalByRefObject { ///  /// Count of remote references to this object. ///  [NonSerialized] private int refCount; ///  /// Creates an object that contains all the relevant information required to /// generate a proxy used to communicate with a remote object. ///  /// The System.Type of the object that the new System.Runtime.Remoting.ObjRef will reference. /// Information required to generate a proxy. [EditorBrowsable(EditorBrowsableState.Never)] public sealed override ObjRef CreateObjRef(Type requestedType) { CrossAppDomainObjRef objRef = new CrossAppDomainObjRef(this, requestedType); return objRef; } ///  /// Disables LifeTime service : object has an infinite life time until it's Disconnected. ///  /// null. [EditorBrowsable(EditorBrowsableState.Never)] public sealed override object InitializeLifetimeService() { return null; } ///  /// Connect a proxy to the object. ///  [EditorBrowsable(EditorBrowsableState.Never)] public void AppDomainConnect() { int value = Interlocked.Increment(ref refCount); Debug.Assert(value > 0); } ///  /// Disconnects a proxy from the object. /// When all proxy are disconnected, the object is disconnected from RemotingServices. ///  [EditorBrowsable(EditorBrowsableState.Never)] public void AppDomainDisconnect() { Debug.Assert(refCount > 0); if (Interlocked.Decrement(ref refCount) == 0) RemotingServices.Disconnect(this); } } 

Malheureusement, cette solution est incorrecte lorsque AppDomains sont utilisés à des fins de plug-in (l’assemblage du plug-in ne doit pas être chargé dans votre domaine principal).

L’appel de GetRealObject () dans votre constructeur et votre destructeur permet d’obtenir le type réel de l’object distant, ce qui conduit à essayer de charger l’assembly de l’object distant dans l’AppDomain actuel. Cela peut provoquer une exception (si l’assembly ne peut pas être chargé) ou l’effet indésirable que vous avez chargé un assembly étranger que vous ne pouvez pas décharger ultérieurement.

Une meilleure solution peut être si vous enregistrez vos objects distants dans votre méthode AppDomain principale avec ClientSponsor.Register () (pas statique, vous devez donc créer une instance de sponsor client). Par défaut, il renouvellera vos proxy distants toutes les 2 minutes, ce qui est suffisant si vos objects ont la durée de vie par défaut de 5 minutes.

J’ai créé une classe qui se déconnecte lors de la destruction.

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

Vous pouvez essayer un object ISponsor singleton sérialisable implémentant IObjectReference. L’implémentation de GetRealObject (à partir de IObjectReference doit renvoyer MySponsor.Instance lorsque context.State est CrossAppDomain, sinon elle doit se renvoyer elle-même. MySponsor.Instance est un singleton auto-initialisant, synchronisé (MethodImplOptions.Synchronized). L’implémentation de renouvellement (à partir d’ISponsor) static MySponsor.IsFlaggedForUnload et renvoyer TimeSpan.Zero lorsqu’il est marqué pour unload / AppDomain.Current.IsFinalizingForUnload () ou renvoyer LifetimeServices.RenewOnCallTime dans le cas contraire.

Pour l’attacher, procurez-vous simplement une ILease et un registre (MySponsor.Instance), qui seront transformés dans le jeu MySponsor.Instance dans AppDomain en raison de l’implémentation de GetRealObject.

Pour arrêter le parrainage, ré-obtenez ILease et Unregister (MySponsor.Instance), puis définissez MySponsor.IsFlaggedForUnload via un rappel cross-AppDomain (myPluginAppDomain.DoCallback (MySponsor.FlagForUnload)).

Cela devrait garder votre object en vie dans l’autre AppDomain jusqu’à l’appel de désinscription, l’appel FlagForUnload ou le déchargement AppDomain.

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

J’ai testé celui-ci et son bon fonctionnement, bien sûr, il faut savoir que le proxy vit pour toujours, jusqu’à ce que vous fassiez GC-ing pour vous-même. Mais dans mon cas, en utilisant un Plugin-Factory connecté à mon application principale, il n’y a pas de fuite de mémoire ou quelque chose du genre. Je me suis juste assuré que j’implémente IDisposable et que ça fonctionne bien (je peux le dire, car mes DLL chargées (en usine) peuvent être écrasées une fois que l’usine est correctement disposée)

Edit: Si vos événements à travers les domaines, ajoutez également cette ligne de code à la classe créant le proxy, sinon vos bulles seront également lancées;)

Si vous souhaitez recréer l’object distant après qu’il a été collecté sans avoir à créer une classe ISponsor ni à lui donner une durée de vie infinie, vous pouvez appeler une fonction fictive de l’object distant lors de la capture d’une 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); } } 

J’ai récemment rencontré cette exception également. À l’heure actuelle, ma solution consiste à décharger AppDomain, puis à recharger AppDomain après un long intervalle. Heureusement, cette solution temporaire fonctionne pour mon cas. Je souhaite qu’il y ait une manière plus élégante de gérer cela.