代码之家  ›  专栏  ›  技术社区  ›  Guillaume

AppDomain和MarshalByRefObject生存期:如何避免RemotingException?

  •  55
  • Guillaume  · 技术社区  · 15 年前

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

    System.Runtime.Remoting.RemotingException异常: 对象[…]已断开连接或 服务器不存在。

    关于此的一些文档:

    如果我错了,请更正我:如果InitializeLifetimeService返回空值,则即使收集了代理,也只能在卸载AppDomain 2时在AppDomain 1中收集对象?

    是否有方法禁用生命周期并在代理完成之前保持代理(在Appdomain2中)和对象(在AppDomain1中)的活动状态?也许有了ISponsor。。。?

    9 回复  |  直到 15 年前
        1
  •  42
  •   scrat.squirrel    14 年前

    请看这里的答案:

    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;
    }
    
        2
  •  13
  •   Guillaume    15 年前

    我终于找到了一种方法来执行客户端激活的实例,但它涉及Finalizer中的托管代码:( 我的类专门用于跨应用程序域通信,但您可以修改它并尝试在其他远程处理。 如果你发现什么虫子就告诉我。

    以下两个类必须位于加载到所有相关应用程序域中的程序集中。

      /// <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);
        }
      }
    
        3
  •  6
  •   taffer    15 年前

    不幸的是,当AppDomains用于插件时,这个解决方案是错误的(插件的程序集不能加载到主appdomain中)。

    构造函数和析构函数中的GetRealObject()调用将获得远程对象的实际类型,这将导致尝试将远程对象的程序集加载到当前AppDomain中。这可能会导致异常(如果无法加载程序集),或者导致加载了以后无法卸载的外部程序集产生不必要的影响。

    一个更好的解决方案是使用client sponsor.register()方法在主AppDomain中注册远程对象(而不是静态的,因此必须创建一个客户端发起实例)。默认情况下,它将在每2分钟更新你的远程代理,如果你的对象有默认的5分钟生存时间就足够了。

        4
  •  1
  •   Squall Leonhart    9 年前

    我创建了一个在销毁时断开连接的类。

    public class MarshalByRefObjectPermanent : MarshalByRefObject
    {
        public override object InitializeLifetimeService()
        {
            return null;
        }
    
        ~MarshalByRefObjectPermanent()
        {
            RemotingServices.Disconnect(this);
        }
    }
    
        5
  •  1
  •   cdiggins    6 年前

    这里有两种可能的解决方案。

    Singleton方法:重写InitializeLifetimeService

    作为 Sacha Goldshtein points out in the blog post 由原始海报链接,如果封送处理的对象具有单例语义,则可以重写 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;
        }
    }
    

    然而,正如用户266748在 another answer

    如果每次创建这样的对象时 客户端连接自身,因为它们永远不会被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中,生命周期管理的工作方式毫无价值,它是 described quite well here on MSDN . 我引用了我认为最有用的部分:

    远程处理生命周期服务将租约与每个服务关联起来, 并在服务的租约到期时删除该服务。一生 服务可以承担传统分布式垃圾的功能 collector,当每个 服务器增加。

    每个应用程序域都包含一个负责 以控制其领域内的租约。所有租约都经过审查 定期为到期的租赁时间。如果租约已经到期,一个或 更多的租约发起人被调用,并有机会 续租。如果没有赞助商决定续租, 租约管理器删除租约,对象可以通过 垃圾收集器。租赁经理在 按剩余租赁时间排序的租赁。最短的租约 剩余时间存储在列表的顶部。远程处理 终身服务将租约与每个服务关联,并删除 租赁期满后提供服务。

        6
  •  0
  •   Paul Pervinkler    10 年前

    您可以尝试一个可序列化的单实例ISponsor对象来实现IObjectReference。当context.State为CrossAppDomain时,GetRealObject实现(来自IObjectReference)应返回MySponsor.Instance,否则返回自身。实例是一个自初始化的、同步的(MethodImplOptions.synchronized)单例。更新实现(来自ISponsor)应检查静态MySponsor.IsFlaggedForUnload,并在标记为unload/AppDomain.Current.isFinalingForUnload()或返回LifetimeServices.RenewOnCallTime时返回TimeSpan.0,否则。

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

    若要停止赞助,请重新获取ILase并注销(MySponsor.Instance),然后通过跨应用程序域回调(myPluginAppDomain.DoCallback(MySponsor.FlagForUnload))设置MySponsor.IsFlaggedForUnload。

    在取消注册调用、FlagForUnload调用或AppDomain unload调用之前,这将使对象在另一个AppDomain中保持活动状态。

        7
  •  0
  •   stephan Schmuck    9 年前
    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
    public override object InitializeLifetimeService()
    {
      return null;
    }
    

    我已经测试过这一个,它的工作很好,当然我们必须知道代理永远存在,直到你为自己做GC ing。但我的情况是,使用一个插件工厂连接到我的主应用,没有内存泄漏或类似的事情。我只是确定,我正在实现IDisposable并且它工作正常(我可以说,因为一旦正确地释放了工厂,我加载的dll(在工厂中)就可以被覆盖)

    编辑:如果您的冒泡事件通过域,请将这一行代码添加到创建代理的类中,否则您的冒泡也将引发;)

        8
  •  0
  •   caiosm1005 Andrew Hare    8 年前

    如果希望在垃圾回收后重新创建远程对象,而不必创建 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);
        }
    }
    
        9
  •  0
  •   Roham Amini    5 年前

    对于那些希望对.NET远程处理框架有更深入了解的人,我建议您阅读以下文章 " Remoting Managing the Lifetime of Remote .NET Objects with Leasing and Sponsorship " 发表于 MSDN Magazine December 2003 issue .

        10
  •  -2
  •   DHornpout    14 年前

    我最近也遇到过这个例外。现在我的解决方案是卸载AppDomain,然后在长时间间隔后重新加载AppDomain。幸运的是,这个临时解决方案对我的案子有效。我希望有一个更优雅的方式来处理这个问题。