简体   繁体   English

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

[英]AppDomain and MarshalByRefObject life time : how to avoid RemotingException?

When a MarshalByRef object is passed from an AppDomain (1) to another (2), if you wait 6 mins before calling a method on it in the second AppDomain (2) you will get a RemotingException :当 MarshalByRef 对象从 AppDomain (1) 传递到另一个 (2) 时,如果在调用第二个 AppDomain (2) 中的方法之前等待 6 分钟,您将收到 RemotingException :

System.Runtime.Remoting.RemotingException: Object [...] has been disconnected or does not exist at the server. System.Runtime.Remoting.RemotingException:对象 [...] 已断开连接或服务器上不存在。

Some documentation about this isse :关于这个问题的一些文档:

Correct me if I'm wrong : if InitializeLifetimeService returns null, the object can only be collected in AppDomain 1 when AppDomain 2 is Unloaded, even if the proxy was collected ?如果我错了,请纠正我:如果 InitializeLifetimeService 返回 null,则只能在 AppDomain 2 卸载时在 AppDomain 1 中收集对象,即使收集了代理?

Is there a way to disable life time and keep the proxy (in AppDomain 2) and the object (in AppDomain1) alive until the proxy is Finalized ?有没有办法禁用生命周期并使代理(在 AppDomain 2 中)和对象(在 AppDomain1 中)保持活动状态,直到代理完成? Maybe with ISponsor... ?也许与ISponsor...?

see answer here:在这里看到答案:

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

which basically says:基本上说:

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

I finally found a way to do client activated instances but it involves managed code in Finalizer :( I specialized my class for CrossAppDomain communication but you may modify it and try in others remoting. Let me know if you find any bug.我终于找到了一种方法来执行客户端激活的实例,但它涉及 Finalizer 中的托管代码 :( 我将我的类专门用于 CrossAppDomain 通信,但您可以修改它并尝试其他远程处理。如果您发现任何错误,请告诉我。

The two following classes must be in an assembly loaded in all application domains involved.以下两个类必须在所有涉及的应用程序域中加载的程序集中。

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

And now the CrossAppDomainObject, your remoted object must inherit from this class instead of MarshalByRefObject.现在是 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);
    }
  }

Unfortunately this solution is wrong when AppDomains are used for plugin purposes (assembly of the plugin must not be loaded into your main appdomain).不幸的是,当 AppDomains 用于插件目的时,此解决方案是错误的(插件程序集不得加载到您的主应用程序域中)。

The GetRealObject() call in your constructor and destructor results in obtaining the real type of the remote object, which leads to trying to load the assembly of the remote object into the current AppDomain.构造函数和析构函数中的 GetRealObject() 调用会导致获取远程对象的真实类型,从而导致尝试将远程对象的程序集加载到当前 AppDomain 中。 This may cause either an exception (if the assembly cannot be loaded) or the unwanted effect that you have loaded a foreign assembly that you cannot unload later.这可能会导致异常(如果无法加载程序集)或您加载了以后无法卸载的外部程序集的不良影响。

A better solution can be if you register your remote objects in your main AppDomain with ClientSponsor.Register() method (not static so you must create a client sponsor instance).更好的解决方案是,如果您使用 ClientSponsor.Register() 方法(不是静态的,因此您必须创建客户端发起人实例)在您的主 AppDomain 中注册您的远程对象。 By default it will renew your remote proxies in every 2 minutes, which is enough if your objects has the default 5 minutes lifetime.默认情况下,它会每 2 分钟更新一次您的远程代理,如果您的对象具有默认的 5 分钟生命周期,这就足够了。

There are two possible solutions here.这里有两种可能的解决方案。

The Singleton approach: Override InitializeLifetimeService单例方法:覆盖 InitializeLifetimeService

As Sacha Goldshtein points out in the blog post linked to by the original poster, if your Marshaled object has Singleton semantics you can override InitializeLifetimeService :正如Sacha Goldshtein在原始海报链接的博客文章中指出的那样,如果您的封送对象具有单例语义,您可以覆盖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;
    }
}

However, as user266748 points out in another answer但是,正如 user266748 在另一个答案中指出的那样

that solution wouldn't work if such an object were created each time a client connects itself, because they would never be GCed and your memory consumption would go up and up until either you stop your server or it crashes because it has no more memory如果每次客户端连接自己时都创建了这样的对象,那么该解决方案将不起作用,因为它们永远不会被 GC 处理,并且您的内存消耗会不断增加,直到您停止服务器或因为它没有更多内存而崩溃

The Class-Based approach: Using ClientSponsor基于类的方法:使用 ClientSponsor

A more general solution is to use ClientSponsor to extend the life of a class-activated remote object.更通用的解决方案是使用ClientSponsor来延长类激活的远程对象的生命周期。 The linked MSDN article has a useful starting example you can follow:链接的 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();
      }
   }
}

It is worth nothing how lifetime management works in the Remoting API, which is described quite well here on MSDN .在 Remoting API 中生命周期管理的工作方式毫无价值, MSDN 上对此进行了很好的描述 I've quoted the part I found most useful:我引用了我认为最有用的部分:

The remoting lifetime service associates a lease with each service, and deletes a service when its lease time expires.远程终身服务将租用与每个服务相关联,并在其租用时间到期时删除服务。 The lifetime service can take on the function of a traditional distributed garbage collector, and it also adjusts well when the numbers of clients per server increases.终身服务既可以承担传统分布式垃圾收集器的功能,也可以在每台服务器的客户端数量增加时进行很好的调整。

Each application domain contains a lease manager that is responsible for controlling leases in its domain.每个应用程序域都包含一个租用管理器,负责控制其域中的租用。 All leases are examined periodically for expired lease times.定期检查所有租约是否有到期租约时间。 If a lease has expired, one or more of the lease's sponsors are invoked and given the opportunity to renew the lease.如果租约已到期,则会调用一个或多个租约的发起人并给予机会续订租约。 If none of the sponsors decides to renew the lease, the lease manager removes the lease and the object can be collected by the garbage collector.如果没有发起者决定续订租约,则租约管理器删除租约,垃圾收集器可以收集对象。 The lease manager maintains a lease list with leases sorted by remaining lease time.租用管理器维护一个租用列表,其中的租用按剩余租用时间排序。 The leases with the shortest remaining time are stored at the top of the list.剩余时间最短的租约存储在列表的顶部。 The remoting lifetime service associates a lease with each service, and deletes a service when its lease time expires.远程生命周期服务将租用与每个服务相关联,并在租用时间到期时删除服务。

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

I created a class which disconnect on destruction.我创建了一个在破坏时断开连接的类。

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

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

If you would like to re-create the remote object after it has been garbage collected without having to create an ISponsor class nor giving it infinite lifetime, you could call a dummy function of the remote object while catching RemotingException .如果您想在垃圾收集后重新创建远程对象,而不必创建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);
    }
}

You could try a serializable singleton ISponsor object implementing IObjectReference.您可以尝试实现 IObjectReference 的可序列化单例 ISponsor 对象。 The GetRealObject implementation (from IObjectReference should return MySponsor.Instance when context.State is CrossAppDomain, otherwise return itself. MySponsor.Instance is a self-initializing, synchronized (MethodImplOptions.Synchronized), singleton. The Renewal implementation (from ISponsor) should check a static MySponsor.IsFlaggedForUnload and return TimeSpan.Zero when flagged for unload/AppDomain.Current.IsFinalizingForUnload() or return LifetimeServices.RenewOnCallTime otherwise. GetRealObject 实现(当 context.State 为 CrossAppDomain 时,来自 IObjectReference 应返回 MySponsor.Instance,否则返回自身。MySponsor.Instance 是自初始化、同步(MethodImplOptions.Synchronized)、单例。Renewal 实现(来自 ISponsor)应检查静态 MySponsor.IsFlaggedForUnload 并在标记为卸载/AppDomain.Current.IsFinalizingForUnload() 时返回 TimeSpan.Zero,否则返回 LifetimeServices.RenewOnCallTime。

To attach it, simply obtain an ILease and Register(MySponsor.Instance), which will be transformed into the MySponsor.Instance set within the AppDomain due to the GetRealObject implementation.要附加它,只需获取一个 ILease 和 Register(MySponsor.Instance),由于 GetRealObject 实现,它将被转换为 AppDomain 中设置的 MySponsor.Instance。

To stop sponsorship, re-obtain the ILease and Unregister(MySponsor.Instance), then set the MySponsor.IsFlaggedForUnload via a cross-AppDomain callback (myPluginAppDomain.DoCallback(MySponsor.FlagForUnload)).要停止赞助,请重新获取 ILease 和 Unregister(MySponsor.Instance),然后通过跨 AppDomain 回调 (myPluginAppDomain.DoCallback(MySponsor.FlagForUnload)) 设置 MySponsor.IsFlaggedForUnload。

This should keep your object alive in the other AppDomain until either the unregister call, the FlagForUnload call, or AppDomain unload.这应该使您的对象在另一个 AppDomain 中保持活动状态,直到取消注册调用、FlagForUnload 调用或 AppDomain 卸载。

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

I've tested this one and its working fine, of course one has to know that the proxy lives forever, until you do GC-ing for yourself.我已经测试了这个并且它工作正常,当然你必须知道代理永远存在,直到你为自己做 GC-ing。 But i my case, using a Plugin-Factory connected to my main app, there is no memory leak or something like this.但我的情况是,使用连接到我的主应用程序的插件工厂,没有内存泄漏或类似的问题。 I just made sure, that i'm implementing IDisposable and it's working fine (I can tell, because my loaded dll's (in the factory) can be overwriten once the factory is disposed correctly)我只是确定,我正在实施 IDisposable 并且它工作正常(我可以说,因为一旦工厂正确处置,我加载的 dll(在工厂中)可以被覆盖)

Edit: If your bubbling events through the domains, add this line of code to the Class creating the proxy as well, otherwise your bubbling will throw too ;)编辑:如果您的冒泡事件通过域,也将这行代码添加到创建代理的类中,否则您的冒泡也会抛出;)

I recently ran into this exception also.我最近也遇到了这个异常。 Right now my solution is just unload AppDomain and then reload AppDomain after a long interval.现在我的解决方案是卸载 AppDomain,然后在很长一段时间后重新加载 AppDomain。 Luckily this temporary solution work for my case.幸运的是,这个临时解决方案适用于我的情况。 I wish there is a more elegant way to deal with this.我希望有一种更优雅的方法来处理这个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM