当MarshalByRef对象从AppDomain(1)传递到另一个(2)时,如果你在第二个AppDomain(2)中调用方法之前等待6分钟,你将得到一个RemotingException:
System.Runtime.Remoting.RemotingException: 对象[...]已断开连接或 在服务器上不存在。
有关此问题的一些文档:
如果我错了,请纠正我:如果InitializeLifetimeService返回null,那么当AppDomain 2被卸载时,该对象只能在AppDomain 1中收集,即使收集了代理?
有没有办法禁用生命时间并使代理(在AppDomain 2中)和对象(在AppDomain1中)保持活动状态,直到代理完成为止?也许与ISponsor ......?
答案 0 :(得分:41)
见答案:
基本上说:
[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)
这里有两种可能的解决方案。
正如原始海报所链接的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
来延长类激活的远程对象的寿命。链接的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。幸运的是,这个临时解决方案适合我的情况。我希望有一种更优雅的方式来解决这个问题。