2010-03-09 11 views
50

Kiedy obiekt MarshalByRef jest przekazywana z AppDomain (1) do innego (2), jeśli czekać 6 minut przed wywołaniem metody na nim w drugim AppDomain (2) dostaniesz RemotingException:AppDomain i MarshalByRefObject czas życia: jak uniknąć wyjątku RemotingException?

System.Runtime.Remoting.RemotingException: Obiekt [...] został odłączony lub na serwerze nie istnieje .

Niektóre dokumentację o tym ISSE: - "Mamy powinny naprawić to"

poprawcie mnie jeśli się mylę: jeśli InitializeLifetimeService zwraca null, obiekt może być gromadzone jedynie w AppDomain 1, gdy AppDomain 2 jest rozładowany, nawet w przypadku pełnomocnika zebrano

Czy istnieje sposób, aby wyłączyć czas życia i utrzymywać serwer proxy (w AppDomain 2) i obiekt (w AppDomain1) przy życiu, dopóki serwer proxy nie zostanie sfinalizowany? Może z ISponsor ...?

Odpowiedz

12

W końcu znalazłem sposób na uruchamianie klienta instancji, ale obejmuje on kod zarządzany w Finalizer :( Wyspecjalizowałem swoją klasę dla komunikacji CrossAppDomain, ale możesz ją zmodyfikować i spróbować w innych zdalnych komputerach.Daj mi znać, jeśli znajdziesz jakiś błąd.

Dwie następujące klasy muszą znajdować się w zespole załadowanym we wszystkich powiązanych domenach aplikacji.

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

A teraz CrossAppDomainObject Twój odludziu obiekt musi dziedziczyć od tej klasy zamiast 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); 
    } 
    } 
+0

Gdybym mógł to zrobić więcej niż raz, zrobiłbym to. Dało mi to dobry początek, rozwiązując podobny problem. – xcud

+0

Chciałbym móc to powtórzyć 5 razy. 'RemotingServices.Disconnect()' to _ klucz. – nbevans

+0

Dzięki To naprawdę mi pomogło – Twisted

6

Niestety rozwiązanie to jest źle, gdy AppDomains są wykorzystywane do celów wtyczki (montaż wtyczki nie musi być załadowany do głównego AppDomain).

Wywołanie funkcji GetRealObject() w konstruktorze i destruktorze powoduje uzyskanie rzeczywistego typu obiektu zdalnego, co prowadzi do próby załadowania złożenia zdalnego obiektu do bieżącego elementu AppDomain. Może to spowodować albo wyjątek (jeśli nie można załadować zespołu), albo niechciany efekt, który załadował obcy zestaw, którego później nie można zwolnić.

Lepszym rozwiązaniem może być rejestrowanie obiektów zdalnych w głównej aplikacji AppDomain za pomocą metody ClientSponsor.Register() (nie statyczne, dlatego należy utworzyć instancję klienta klienta). Domyślnie odnawia twoje zdalne serwery proxy co 2 minuty, co jest wystarczające, jeśli obiekty mają domyślne 5-minutowe życie.

+0

Dodałem base.TypeInfo.TypeName = typeof (CrossAppDomainObject) .AssemblyQualifiedName; w CrossAppDomainObjRef ctor, ale nadal nie udaje się w niektórych przypadkach, a dalsze liczenie może prowadzić do wycieku na okrągłych odniesieniach ... – Guillaume

-2

Niedawno spotkałem się również z tym wyjątkiem. Teraz moim rozwiązaniem jest po prostu zwolnienie AppDomain, a następnie ponowne załadowanie AppDomain po dłuższej przerwie. Na szczęście to tymczasowe rozwiązanie działa w moim przypadku. Żałuję, że nie ma bardziej eleganckiego sposobu poradzenia sobie z tym.

+2

Jest. ISponsor. – Will

36

zobaczyć odpowiedź tutaj:

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

które zasadniczo mówi:

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

Obiekt pozostanie połączony i wkrótce skończy się zasoby, jeśli masz wiele obiektów zdalnych. Druga część mojego pytania dotyczy InitializeLifetimeService zwracającego wartość null. – Guillaume

+0

W rzeczywistości mam tylko jeden obiekt do zdalnego przekazywania. Operacja może potrwać bardzo długo (na podstawie danych użytkownika może to potrwać kilka dni ...). Za pomocą tej implementacji nie brakuje zasobów - przetestowałem i przetestowałem ponownie. – woohoo

+5

Um ... kilka osób zarzuciło to, nie mówiąc ani słowa, dlaczego to zrobili. Chociaż może to nic nie oznaczać, dobrze byłoby wiedzieć, dlaczego (z punktu widzenia cywilizacji ...). Również to rozwiązanie działa bardzo dobrze w prawdziwej komercyjnej aplikacji, nie tylko wyciągnąłem go z kapelusza. – woohoo

0

Mógłbyś spróbować można zaszeregować singleton ISponsor obiekt wykonawczych IObjectReference. Implementacja GetRealObject (z IObjectReference powinna zwrócić MySponsor.Instance, gdy context.State jest CrossAppDomain, w przeciwnym razie zwraca się.MoSponsor.Instance jest samo inicjującym, zsynchronizowanym (MethodImplOptions.Synchronized), singleton. Implementacja odnowienia (od ISponsor) powinna sprawdzić statyczne MySponsor.IsFlaggedForUnload i powrócić TimeSpan.Zero gdy na pozycji rozładunku/AppDomain.Current.IsFinalizingForUnload() lub powrót LifetimeServices.RenewOnCallTime inaczej.

Aby dołączyć go, po prostu uzyskać ILease i rejestru (MySponsor.Instance), który będzie zostać przekształcone w zestaw MySponsor.Instance w AppDomain ze względu na implementację GetRealObject

Aby zatrzymać sponsorowanie, ponownie uzyskaj ILease i wyrejestruj (MySponsor.Insta nce), a następnie ustawić MySponsor.IsFlaggedForUnload przez połączenie zwrotne AppDomain (myPluginAppDomain.DoCallback (MySponsor.FlagForUnload)).

ta powinna zachować swój obiekt żyje w innym AppDomain aż albo wyrejestrować wezwanie, wezwanie FlagForUnload lub AppDomain rozładować.

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

Testowałem ten jeden i działa dobrze, oczywiście trzeba wiedzieć, że proxy pozostaje na zawsze, dopóki nie zrobisz GC-ing dla siebie. Ale w moim przypadku, używając Plugin-Factory podłączonej do mojej głównej aplikacji, nie ma wycieku pamięci ani czegoś takiego. Właśnie upewniłem się, że implementuję IDisposable i działa dobrze (mogę powiedzieć, ponieważ moje załadowane biblioteki dll (w fabryce) mogą zostać nadpisane po prawidłowym umieszczeniu fabryki)

Edycja: Jeśli Twoje bulgotanie przez wydarzenia domeny, należy dodać następujący wiersz kodu do klasy tworząc pełnomocnika, jak również, w przeciwnym razie bulgotanie rzuci też;)

1

stworzyłem klasę która odłączyć na zniszczenia.

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

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

Jeśli chcesz ponownie utworzyć obiekt zdalny po jego śmieci zbierane bez konieczności tworzenia klasy ISponsor ani nadając mu nieskończoną żywotność, można nazwać obojętne funkcję zdalnego obiektu podczas łapania 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); 
    } 
} 
Powiązane problemy