AppDomain和MarshalByRefObject的生命时间:如何避免RemotingException?

当一个MarshalByRef对象从一个AppDomain(1)传递到另一个(2)时,如果你在第二个AppDomain(2)中调用一个方法之前等待6分钟,你会得到一个RemotingException:

System.Runtime.Remoting.RemotingException:Object […]已被断开或在服务器上不存在。

有关这方面的一些文件:

  • 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 – 实例一生,cbrumme说:“我们应该解决这个问题。 🙁

纠正我,如果我错了:如果InitializeLifetimeService返回null,对象只能在AppDomain 2被卸载时收集在AppDomain 1中,即使代理被收集?

有没有一种方法来禁用生命时间,并保持代理(在AppDomain 2)和对象(在AppDomain1)活着,直到代理被定案? 也许与ISponsor …?

在这里看到答案:

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

我终于find了一种方法来做客户端激活的实例,但它涉及托pipe代码在终结者:(我专门为我的类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,你的远程对象必须inheritance这个类而不是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); } } 

不幸的是,当AppDomain用于插件目的时,这个解决scheme是错误的(插件的组装不能被加载到你的主appdomain中)。

在构造函数和析构函数中的GetRealObject()调用将导致获取远程对象的实际types,从而导致尝试将远程对象的程序集加载到当前AppDomain中。 这可能会导致exception(如果程序集无法加载)或者加载了以后无法卸载的外部程序集的不良影响。

如果使用ClientSponsor.Register()方法(不是静态的,所以您必须创build一个客户端主办方实例)将您的远程对象注册到您的主AppDomain中,更好的解决scheme可能是。 默认情况下,它会每2分钟更新一次远程代理,如果你的对象有5分钟的默认生命周期,这就足够了。

我创build了一个在破坏时断开连接的类。

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

你可以尝试一个实现IObjectReference的可序列化的singleton ISponsor对象。 GetRealObject实现(来自IObjectReference应该返回MySponsor.Instance当context.State是CrossAppDomain,否则返回自己。MySponsor.Instance是一个自我初始化,同步(MethodImplOptions.Synchronized),单身。更新实现(从ISponsor)应该检查静态MySponsor.IsFlaggedForUnload并返回TimeSpan.Zero标记为卸载/ AppDomain.Current.IsFinalizingForUnload()否则返回LifetimeServices.RenewOnCallTime。

要附加它,只需获取一个ILease和Register(MySponsor.Instance),由于GetRealObject的实现,它将被转换成AppDomain中的MySponsor.Instance集合。

要停止赞助,请重新获取ILease和Unregister(MySponsor.Instance),然后通过跨AppDomaincallback(myPluginAppDomain.DoCallback(MySponsor.FlagForUnload))设置MySponsor.IsFlaggedForUnload。

这应该保持您的对象在另一个AppDomain中存活,直到unregister调用,FlagForUnload调用或AppDomain卸载。

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

我已经testing过这个,它的工作正常,当然,我们必须知道,这个代理永远存在,直到你为自己做GC。 但我的情况下,使用连接到我的主应用程序的插件工厂,没有内存泄漏或类似的东西。 我只是确定,我正在实施IDisposable,它工作正常(我可以告诉,因为我的加载DLL(在工厂)可以overwriten一旦工厂正确处置)

编辑:如果你的冒泡事件通过域,将这行代码添加到创build代理的类,否则你的冒泡也会抛出;)

如果你想在垃圾收集之后重新创build远程对象,而不需要创build一个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); } } 

我最近还遇到了这个例外。 现在我的解决scheme就是卸载AppDomain,然后在长时间间隔后重新加载AppDomain。 幸运的是,这个临时解决scheme适用于我的情况 我希望有一个更优雅的方式来处理这个问题。