web-dev-qa-db-ja.com

AppDomainとMarshalByRefObjectのライフタイム:RemotingExceptionを回避する方法は?

MarshalByRefオブジェクトがAppDomain(1)から別の(2)に渡されると、2番目のAppDomain(2)でメソッドを呼び出す前に6分間待機すると、RemotingExceptionが発生します。

System.Runtime.Remoting.RemotingException:オブジェクト[...]が切断されたか、サーバーに存在しません。

このisseに関するいくつかのドキュメント:

私が間違っている場合は修正してください:InitializeLifetimeServiceがnullを返す場合、プロキシが収集された場合でも、AppDomain 2がアンロードされると、AppDomain 1でのみオブジェクトを収集できますか?

ライフタイムを無効にし、プロキシがファイナライズされるまでプロキシ(AppDomain 2内)とオブジェクト(AppDomain1内)を有効に保つ方法はありますか?たぶんISponsorと...?

56
Guillaume

こちらの回答をご覧ください:

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

最終的にクライアントアクティブ化インスタンスを実行する方法を見つけましたが、Finalizerのマネージコードが含まれます:.

次の2つのクラスは、関連するすべてのアプリケーションドメインにロードされたアセンブリに存在する必要があります。

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

残念ながら、プラグインの目的でAppDomainを使用する場合、このソリューションは間違っています(プラグインのアセンブリをメインのappdomainにロードしてはいけません)。

コンストラクターとデストラクターでのGetRealObject()呼び出しにより、リモートオブジェクトの実際の型が取得され、リモートオブジェクトのアセンブリを現在のAppDomainに読み込もうとします。これにより、例外(アセンブリをロードできない場合)または、後でアンロードできない外部アセンブリをロードしたという不要な影響が発生する可能性があります。

より良い解決策は、ClientSponsor.Register()メソッドを使用してメインAppDomainにリモートオブジェクトを登録することです(静的ではないため、クライアントスポンサーインスタンスを作成する必要があります)。デフォルトでは、2分ごとにリモートプロキシが更新されます。これは、オブジェクトのデフォルトの有効期間が5分であれば十分です。

6
taffer

ここには2つの解決策があります。

シングルトンアプローチ: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;
    }
}

ただし、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で非常によく説明されています です。最も有用だと思った部分を引用しました。

リモーティングライフタイムサービスは、リースを各サービスに関連付け、リース期間が終了するとサービスを削除します。ライフタイムサービスは、従来の分散ガベージコレクターの機能を引き受けることができ、サーバーあたりのクライアント数が増加した場合にも適切に調整されます。

各アプリケーションドメインには、そのドメインのリースを制御するリースマネージャーが含まれています。すべてのリースは、期限切れのリース時間について定期的に検査されます。リースの期限が切れた場合、1つ以上のリースのスポンサーが呼び出され、リースを更新する機会が与えられます。スポンサーのいずれもリースの更新を決定しない場合、リースマネージャーはリースを削除し、オブジェクトはガベージコレクターによって収集できます。リースマネージャは、残りのリース時間でソートされたリースでリースリストを維持します。残り時間が最短のリースは、リストの一番上に保存されます。リモーティングライフタイムサービスは、リースを各サービスに関連付け、リース期間が終了するとサービスを削除します。

1
cdiggins

破壊時に切断するクラスを作成しました。

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

    ~MarshalByRefObjectPermanent()
    {
        RemotingServices.Disconnect(this);
    }
}
1
Squall Leonhart

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);
    }
}
0
caiosm1005
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
  return null;
}

私はこれをテストし、正常に動作します。もちろん、自分でGCを実行するまで、プロキシが永久に存続することを知っておく必要があります。しかし、私の場合、メインアプリに接続されたPlugin-Factoryを使用すると、メモリリークなどは発生しません。私はIDisposableを実装しており、正常に動作していることを確認しました(工場が正しく破棄されると、ロードされたdll(工場内)が上書きされる可能性があるため、わかります)

編集:ドメインを介したバブリングイベントの場合、このコード行をプロキシに作成するクラスに追加します。そうしないと、バブリングもスローされます;)

0
stephan Schmuck

IObjectReferenceを実装するシリアル化可能なシングルトンISponsorオブジェクトを試すことができます。 GetRealObject実装(context.StateがCrossAppDomainの場合、IObjectReferenceからMySponsor.Instanceを返す必要があります。それ以外の場合、MySponsor.Instanceは自己初期化、同期(MethodImplOptions.Synchronized)、シングルトンです。Renewal実装(ISponsorから) static 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を設定します。

これにより、登録解除呼び出し、FlagForUnload呼び出し、またはAppDomainアンロードのいずれかが行われるまで、オブジェクトは他のAppDomainで存続します。

0
Paul Pervinkler