Gui*_*ume 56 .net c# 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 ......?
woo*_*hoo 41
看到答案:
基本上说:
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
return null;
}
Run Code Online (Sandbox Code Playgroud)
Gui*_*ume 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));
}
}
Run Code Online (Sandbox Code Playgroud)
现在是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);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 6
不幸的是,当AppDomains用于插件时,此解决方案是错误的(插件的程序集不能加载到您的主appdomain中).
构造函数和析构函数中的GetRealObject()调用会导致获取远程对象的实际类型,从而导致尝试将远程对象的程序集加载到当前的AppDomain中.这可能会导致异常(如果无法加载程序集)或者加载了以后无法卸载的外部程序集的不良影响.
如果您使用ClientSponsor.Register()方法在主AppDomain中注册远程对象(不是静态的,那么您必须创建客户端发起人实例),这是一个更好的解决方案.默认情况下,它将每2分钟更新一次远程代理,如果您的对象具有默认的5分钟生命周期就足够了.
这里有两种可能的解决方案。
正如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;
}
}
Run Code Online (Sandbox Code Playgroud)
但是,正如 user266748 在另一个答案中指出的那样
如果每次客户端连接自己时都创建了这样的对象,那么该解决方案将不起作用,因为它们永远不会被 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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
在 Remoting API 中生命周期管理的工作方式毫无价值,MSDN 上对此进行了很好的描述。我引用了我认为最有用的部分:
远程终身服务将租用与每个服务相关联,并在其租用时间到期时删除服务。终身服务既可以承担传统分布式垃圾收集器的功能,也可以在每台服务器的客户端数量增加时进行很好的调整。
每个应用程序域都包含一个租用管理器,负责控制其域中的租用。定期检查所有租用是否已过期。如果租约已到期,则会调用一个或多个租约的发起人并给予机会续订租约。如果没有发起者决定续订租约,则租约管理器删除租约,垃圾收集器可以收集对象。租用管理器维护一个租用列表,其中的租用按剩余租用时间排序。剩余时间最短的租约存储在列表的顶部。远程终身服务将租用与每个服务相关联,并在其租用时间到期时删除服务。
归档时间: |
|
查看次数: |
28107 次 |
最近记录: |