62

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

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

关于这个问题的一些文档:

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

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

4

10 回答 10

46

在这里查看答案:

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;
}
于 2011-05-24T13:21:02.650 回答
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);
    }
  }
于 2010-03-11T08:19:47.750 回答
7

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

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

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

于 2010-04-16T16:17:48.717 回答
6

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

单例方法:重写 InitializeLifetimeService

正如Sacha Goldshtein在原始海报链接的博客文章中指出的那样InitializeLifetimeService,如果您的 Marshaled 对象具有 Singleton 语义,您可以覆盖:

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 在另一个答案中指出的那样

如果每次客户端连接自己时都创建这样的对象,则该解决方案将不起作用,因为它们永远不会被 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 中的工作方式毫无价值,这在 MSDN 上有很好的描述。我引用了我认为最有用的部分:

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

每个应用程序域都包含一个租用管理器,负责控制其域中的租用。定期检查所有租约是否过期。如果租约已到期,则会调用一个或多个租约发起人并给予续约机会。如果没有赞助商决定更新租约,则租约管理器删除租约,垃圾收集器可以收集对象。租赁管理器维护一个租赁列表,其中包含按剩余租赁时间排序的租赁。剩余时间最短的租约存储在列表的顶部。远程生命周期服务将租约与每个服务相关联,并在其租约时间到期时删除服务。

于 2019-01-27T17:29:36.980 回答
2

对于那些希望更深入地了解 .NET Remoting Framework 的人,我建议您在MSDN 杂志 2003 年 12 月号上发表题为Remoting 通过租赁和赞助管理远程 .NET 对象的生命周期的文章。

于 2019-10-31T02:12:44.457 回答
1

如果您想在远程对象被垃圾回收后重新创建它,而不必创建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);
    }
}
于 2016-10-08T22:50:22.893 回答
1

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

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

    ~MarshalByRefObjectPermanent()
    {
        RemotingServices.Disconnect(this);
    }
}
于 2016-02-24T03:28:53.097 回答
0

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

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

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

这应该使您的对象在另一个 AppDomain 中保持活动状态,直到取消注册调用、FlagForUnload 调用或 AppDomain 卸载。

于 2014-06-09T20:56:28.797 回答
0
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
  return null;
}

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

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

于 2015-10-13T09:59:33.827 回答
-2

我最近也遇到了这个异常。现在我的解决方案只是卸载 AppDomain,然后在很长一段时间后重新加载 AppDomain。幸运的是,这个临时解决方案适用于我的情况。我希望有一种更优雅的方式来处理这个问题。

于 2011-01-24T18:32:06.357 回答