AppDomain和MarshalByRefObject的生命周期:如何避免RemotingException?

时间:2010-03-09 15:36:19

标签: c# .net remoting appdomain object-lifetime

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

  

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

有关此问题的一些文档:

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

有没有办法禁用生命时间并使代理(在AppDomain 2中)和对象(在AppDomain1中)保持活动状态,直到代理完成为止?也许与ISponsor ......?

10 个答案:

答案 0 :(得分:41)

见答案:

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

答案 1 :(得分:13)

我终于找到了一种方法来做客户端激活的实例,但它涉及到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'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);
    }
  }

答案 2 :(得分:6)

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

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

如果您使用ClientSponsor.Register()方法在主AppDomain中注册远程对象,则可以使用更好的解决方案(不是静态的,因此您必须创建客户端发起人实例)。默认情况下,它将每2分钟更新一次远程代理,如果您的对象具有默认的5分钟生命周期就足够了。

答案 3 :(得分:1)

我创建了一个断开连接的类。

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

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

答案 4 :(得分:0)

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

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

要停止赞助,请重新获取ILease并取消注册(MySponsor.Instance),然后通过跨AppDomain回调设置MySponsor.IsFlaggedForUnload(myPluginAppDomain.DoCallback(MySponsor.FlagForUnload))。

这将使您的对象在其他AppDomain中保持活动状态,直到取消注册调用,FlagForUnload调用或AppDomain卸载为止。

答案 5 :(得分:0)

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

我已经测试了这个并且它的工作正常,当然人们必须知道代理永远存在,直到你为自己做GC-ing。但我的情况是,使用连接到我的主应用程序的插件工厂,没有内存泄漏或类似的东西。我只是确定,我正在实施IDisposable并且它工作正常(我可以告诉,因为我的加载的dll(在工厂中)可以在工厂正确处理后被覆盖)

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

答案 6 :(得分:0)

如果您希望在垃圾收集后重新创建远程对象而不必创建ISponsor类,也不给它无限的生命周期,则可以在捕获时调用远程对象的虚函数{ {1}}。

RemotingException

答案 7 :(得分:0)

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

Singleton方法:覆盖InitializeLifetimeService

正如原始海报所链接的Sacha Goldshtein points out in the blog post一样,如果封送处理的对象具有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;
    }
}

但是,正如user266748在another answer中指出的

  

如果每次创建一个这样的对象,该解决方案将不起作用   客户端自行连接,因为它们永远不会被GC,并且您的   内存消耗会不断上升,直到您停止   服务器或由于没有更多内存而崩溃

基于类的方法:使用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(described quite well here on MSDN)中工作毫无意义。我引用了我发现最有用的部分:

  

远程生命周期服务将租约与每个服务相关联,   并在其租约时间到期时删除服务。一生   服务可以承担传统分布式垃圾的功能   收集器,并且当每个客户的数量   服务器增加。

     

每个应用程序域都包含一个负责的租约管理器   用于控制其域中的租赁。所有租赁都经过审查   定期租约到期。如果租约已过期,则一个或   更多的租赁发起人被调用,并有机会   续约。如果没有任何保荐人决定续约,   租赁管理者删除租赁,并且可以通过以下方式收集对象:   垃圾收集器。租赁经理维护一份租赁清单,其中包括   租赁按剩余租赁时间排序。最短的租赁   剩余时间存储在列表的顶部。远程处理   终身服务将租赁与每个服务相关联,并删除   租约时间到期时提供服务。

答案 8 :(得分:0)

对于那些希望更深入地了解.NET Remoting Framework的人,我建议在Remoting Managing the Lifetime of Remote .NET Objects with Leasing and Sponsorship中发表标题为MSDN Magazine December 2003 issue的文章。

答案 9 :(得分:-2)

我最近也碰到了这个例外。现在我的解决方案只是卸载AppDomain,然后在很长的时间间隔后重新加载AppDomain。幸运的是,这个临时解决方案适合我的情况。我希望有一种更优雅的方式来解决这个问题。