Внедряване на модел за финализирано изхвърляне с множество свързани финализируеми обекти

До голяма степен съм запознат с модела Dispose за нефинализируеми типове, например типове, които обгръщат някакъв вид управляван ресурс, върху който искаме да се направи детерминистично почистване. Този вид типове обикновено не имплементират финализатор, тъй като той е напълно ненужен.

Въпреки това внедрявам обвивка на C# за естествен API, където обгръщам множество, свързани неуправляеми ресурси, и изглежда ще има нужда от множество класове, всеки от които прилага шаблона за финализирано изхвърляне. Проблемът е, че указанията за шаблона за изхвърляне казват, че finalizable A не трябва да разчита на finalizable B, което е точно това, от което се нуждая:

Изхвърляне на образец в MSDN:

X НЕ осъществявайте достъп до финализиращи обекти в пътя на кода на финализатора, тъй като съществува значителен риск те вече да са финализирани.

Например, финализиращ обект A, който има препратка към друг финализиращ обект B, не може надеждно да използва B във финализатора на A, или обратното. Финализаторите се извикват в произволен ред (с изключение на слаба гаранция за подреждане за критично финализиране).

И така, ето моите ограничения:

  • За да направя нещо, трябва да създам манипулатор "API".
  • За да създам манипулатори на „дете“, трябва да осигуря манипулатора на API в извикването „създаване на дете“.
  • И двата вида дръжки не могат да изтекат.
  • Ако манипулаторът на API е затворен, всички негови дъщерни манипулатори са имплицитно затворени.
  • За да затворя дъщерен манипулатор, трябва да осигуря дъщерния манипулатор и API манипулатора в основното извикване.

Родният API изглежда по следния начин:

APIHANDLE GizmoCreateHandle();

CHILDHANDLE GizmoCreateChildHandle( APIHANDLE apiHandle );

GizmoCloseHandle( APIHANDLE apiHandle );

GizmoCloseChildHandle( APIHANDLE apiHandle, CHILDHANDLE childHandle);

Наивният подход за това би бил две части:

  • За манипулатора на API следвайте типичния модел SafeHandle.
  • За дъщерния манипулатор, следвайте типичния модел SafeHandle, но го променете малко, тъй като дъщерният манипулатор се нуждае от препратка към API манипулатора, за да приложи своята замяна на ReleaseHandle - добавете метод, който дава API манипулатора на дъщерния SafeHandle след построено е.

Така че всичко ще изглежда така:

    [DllImport( "gizmo.dll" )]
    private static extern ApiSafeHandle GizmoCreateHandle();

    [DllImport( "gizmo.dll" )]
    private static extern void GizmoCloseHandle( IntPtr apiHandle );

    [DllImport( "gizmo.dll" )]
    private static extern ChildSafeHandle GizmoCreateChildHandle(ApiSafeHandle apiHandle);

    [DllImport( "gizmo.dll" )]
    private static extern void GizmoCloseChildHandle( ApiSafeHandle apiHandle, IntPtr childHandle );

    [DllImport( "gizmo.dll" )]
    private static extern void GizmoChildModify( ChildSafeHandle childHandle, int flag );

    public class ApiSafeHandle : SafeHandle
    {
        public ApiSafeHandle() : base( IntPtr.Zero, true ) { }

        public override bool IsInvalid
        {
            get { return this.handle == IntPtr.Zero; }
        }

        protected override bool ReleaseHandle()
        {
            GizmoCloseHandle( this.handle );
            return true;
        }
    }

    public class ChildSafeHandle : SafeHandle
    {
        private ApiSafeHandle apiHandle;

        public ChildSafeHandle() : base( IntPtr.Zero, true ) { }

        public override bool IsInvalid
        {
            get { return this.handle == IntPtr.Zero; }
        }

        public void SetParent( ApiSafeHandle handle )
        {
            this.apiHandle = handle;
        }

        // This method is part of the finalizer for SafeHandle.
        // It access its own handle plus the API handle, which is also a SafeHandle
        // According to MSDN, this violates the rules for finalizers.
        protected override bool ReleaseHandle()
        {
            if ( this.apiHandle == null )
            {
                // We were used incorrectly - we were allocated, but never given 
                // the means to deallocate ourselves
                return false;
            }
            else if ( this.apiHandle.IsClosed )
            {
                // Our parent was already closed, which means we were implicitly closed.
                return true;
            }
            else
            {
                GizmoCloseChildHandle( apiHandle, this.handle );
                return true;
            }
        }
    }

    public class GizmoApi
    {
        ApiSafeHandle apiHandle;

        public GizmoApi()
        {
            this.apiHandle = GizmoCreateHandle();
        }

        public GizmoChild CreateChild()
        {
            ChildSafeHandle childHandle = GizmoCreateChildHandle( this.apiHandle );

            childHandle.SetParent( this.apiHandle );

            return new GizmoChild( childHandle );
        }
    }

    public class GizmoChild
    {
        private ChildSafeHandle childHandle;

        internal GizmoChild( ChildSafeHandle handle )
        {
            this.childHandle = handle;
        }

        public void SetFlags( int flags )
        {
            GizmoChildModify( this.childHandle, flags );
        }

        // etc.
    }

Сега обаче имам недостатък - ReleaseHandle на моя ChildSafeHandle препраща към друг манипулатор, за да върши работата си. Сега имам два финализирани обекта за еднократна употреба, където финализаторът на единия зависи от другия. MSDN изрично казва, че финализаторите не трябва да разчитат на други обекти, които могат да бъдат финализирани, тъй като те може вече да са финализирани или ако .Net някога поддържа многонишково финализиране, те може да се финализират едновременно.

Какъв е правилният начин да направите това? Правилата позволяват ли ми достъп до финализиран обект A от финализатора на B, стига първо да тествам за валидност? MSDN не е ясен по този въпрос.


person antiduh    schedule 23.12.2013    source източник
comment
Има основателна причина, поради която обичаме потребителите да публикуват фрагмент. Както е, то е неясно, но не звучи като че ли се случва нещо специално. По време на финализирането просто няма значение, че финализиращ обект има препратка към друг финализиращ обект. Вие се интересувате от това, когато внедрявате IDisposable, това е всичко.   -  person Hans Passant    schedule 24.12.2013
comment
Ключовият въпрос е: да предположим, че родителският манипулатор е финализиран преди дъщерния манипулатор. Това ще доведе ли до неуспех при финализирането на детето?   -  person Eric Lippert    schedule 24.12.2013
comment
Ако оригиналният родителски манипулатор е затворен преди собствения дъщерен манипулатор, тогава всичко се изчиства и всяко финализиране/разпореждане на дъщерния елемент в .Net-land не трябва да прави нищо.   -  person antiduh    schedule 24.12.2013
comment
И коментаторът, и близката причина ви казват защо: проблемите с код трябва да включват най-краткото количество код, необходимо за възпроизвеждане на проблема - в самия въпрос. Не е разрешено свързване към външен ресурс, който съдържа проекта. Ето защо.   -  person George Stocker    schedule 29.06.2014


Отговори (1)


Важното нещо, което трябва да запомните относно финализирането, е, че събирачът на боклук гарантира, че няма да изтрие полетата, свързани с който и да е обект, освен ако не може да гарантира, че никога няма да съществува препратка към този обект, но не дава гаранция за последователност или контекст на нишка, с който ще извика Finalize на обект, нито има никакъв контрол върху това, което Finalize метод (или който и да е друг) може да направи с обект.

Ако foo съдържа препратка към bar и не съществува препратка към нито едно от тях никъде другаде освен в опашката за финализиране, системата може да извика Finalize на bar преди или след като извика Finalize на foo. Ако bar и foo поддържат препратки един към друг, и и двата са съгласни как да координират почистването, може да е възможно кой от тях да има своя Finalize метод, извикан първи, за да изчисти двата обекта в каквато и последователност да изисква семантиката на типа. Няма обаче общоприет модел за това; всеки, който прилага такова нещо, трябва да организира собствената си координация.

Обърнете внимание също, че WeakReference има една доста досадна странност: ако някой подаде true към конструктора на WeakReference, фактът, че целта му има регистриран финализатор, ще предотврати анулирането му, докато този финализатор не се изпълни или стане нерегистриран, но< /em> ако не съществува силна препратка към самия WeakReference, неговият финализатор ще го направи невалиден дори ако целта все още е валидна. По този начин, в сценария по-горе, ако bar съдържа WeakReference до foo, ще има включени три финализатора: този на foo, този на bar и този на WeakReference на bar. Ако foo трябва първо да бъде изчистен, единственият начин bar да постигне това е или да държи силна препратка, или да съхранява WeakReference някъде, което е достъпно чрез статична препратка [което само по себе си създава опасности].

person supercat    schedule 24.12.2013
comment
Ти си фар на здравия разум. Благодаря Ви за отговора. - person antiduh; 24.12.2013