هل سيقوم جامع البيانات المهملة بالاتصال بـ IDisposable.Dispose لي؟

StackOverflow https://stackoverflow.com/questions/45036

  •  09-06-2019
  •  | 
  •  

سؤال

الشبكة نمط معرف القابل للتصرف يدل أنه إذا قمت بكتابة أداة نهائية، وقمت بتنفيذ IDisposable، فإن أداة الإنهاء الخاصة بك تحتاج إلى استدعاء Dispose بشكل صريح.هذا أمر منطقي، وهو ما كنت أفعله دائمًا في المواقف النادرة التي يكون فيها وضع اللمسات النهائية مبررًا.

ومع ذلك، ماذا يحدث إذا قمت بذلك فقط:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

ولا تنفذ أداة نهائية أو أي شيء.هل سيستدعي الإطار طريقة التخلص لي؟

نعم، أدرك أن هذا يبدو غبيًا، وكل المنطق يشير إلى أنه لن يحدث، ولكن كان لدي دائمًا شيئين في مؤخرة رأسي مما جعلني غير متأكد.

  1. أخبرني أحد الأشخاص ذات مرة قبل بضع سنوات أنه سيفعل ذلك في الواقع، وكان لهذا الشخص سجل حافل جدًا في "معرفة أشياءه".

  2. يقوم المترجم/إطار العمل بأشياء "سحرية" أخرى اعتمادًا على الواجهات التي تقوم بتنفيذها (على سبيل المثال:foreach، وأساليب الامتداد، والتسلسل بناءً على السمات، وما إلى ذلك)، لذا فمن المنطقي أن يكون هذا "سحريًا" أيضًا.

على الرغم من أنني قرأت الكثير من الأشياء حول هذا الموضوع، وكان هناك الكثير من الأشياء الضمنية، إلا أنني لم أتمكن مطلقًا من العثور على نهائي نعم أو لا إجابة على هذا السؤال.

هل كانت مفيدة؟

المحلول

يستدعي .Net Garbage Collector أسلوب Object.Finalize لكائن في مجموعة البيانات المهملة.بواسطة تقصير هذا لا لا شئ ويجب تجاوزه إذا كنت تريد تحرير موارد إضافية.

لا يتم استدعاء التخلص تلقائيًا ويجب أن يتم ذلك صريحة يتم استدعاؤها إذا كان سيتم تحرير الموارد، مثل داخل كتلة "استخدام" أو "المحاولة أخيرًا".

يرى http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx للمزيد من المعلومات

نصائح أخرى

أريد أن أؤكد على وجهة نظر بريان في تعليقه، لأنها مهمة.

أدوات الإنهاء ليست أدوات تدمير حتمية كما في لغة C++.كما أشار آخرون، ليس هناك ما يضمن متى سيتم استدعاؤه، وبالفعل إذا كان لديك ذاكرة كافية، إذا كان سيتم استدعاؤه أبدًا يدعى يسمى.

لكن الشيء السيئ في أدوات الإنهاء هو، كما قال بريان، أنها تتسبب في بقاء الكائن الخاص بك على قيد الحياة بعد تجميع القمامة.هذا يمكن أن يكون سيئا.لماذا؟

كما تعلم أو لا تعلم، يتم تقسيم GC إلى أجيال - الجيل 0 والجيل الأول والثاني، بالإضافة إلى كومة الكائنات الكبيرة.الانقسام هو مصطلح فضفاض - تحصل على كتلة واحدة من الذاكرة، ولكن هناك مؤشرات تشير إلى مكان بداية ونهاية كائنات Gen 0.

تتمثل عملية التفكير في أنك ستستخدم على الأرجح الكثير من الأشياء التي ستكون قصيرة العمر.لذلك يجب أن يكون ذلك سهلاً وسريعًا حتى يتمكن GC من الوصول إلى كائنات Gen 0.لذلك عندما يكون هناك ضغط على الذاكرة، فإن أول شيء تفعله هو مجموعة Gen 0.

الآن، إذا لم يحل ذلك ما يكفي من الضغط، فإنه يعود ويجري عملية مسح للجيل الأول (إعادة الجيل 0)، وبعد ذلك إذا لم يكن كافيًا، فإنه يقوم بعملية المسح للجيل الثاني (إعادة الجيل 1 والجيل 0).لذا فإن تنظيف الأشياء طويلة العمر قد يستغرق بعض الوقت ويكون مكلفًا إلى حد ما (حيث قد يتم تعليق خيوطك أثناء العملية).

هذا يعني أنه إذا قمت بشيء مثل هذا:

~MyClass() { }

كائنك، بغض النظر عن ذلك، سوف يعيش إلى الجيل الثاني.وذلك لأن GC ليس لديه طريقة لاستدعاء أداة الإنهاء أثناء تجميع البيانات المهملة.لذلك يتم نقل الكائنات التي يجب إنهاؤها إلى قائمة انتظار خاصة ليتم تنظيفها بواسطة خيط مختلف (خيط الإنهاء - والذي إذا قمت بقتله يؤدي إلى حدوث جميع أنواع الأشياء السيئة).وهذا يعني أن الكائنات الخاصة بك تبقى لفترة أطول، ومن المحتمل أن تفرض المزيد من مجموعات البيانات المهملة.

لذا، كل هذا هو فقط لتوضيح النقطة التي تريد استخدام IDisposable لتنظيف الموارد كلما أمكن ذلك ومحاولة جدية لإيجاد طرق حول استخدام أداة الإنهاء.إنه في مصلحة تطبيقك.

هناك الكثير من المناقشات الجيدة هنا بالفعل، ولقد تأخرت قليلاً عن الحفلة، ولكنني أردت إضافة بعض النقاط بنفسي.

  • لن يقوم مجمّع البيانات المهملة مطلقًا بتنفيذ طريقة التخلص نيابةً عنك.
  • جي سي سوف تنفيذ Finalizers عندما تشعر بذلك.
  • أحد الأنماط الشائعة المستخدمة للكائنات التي تحتوي على أداة نهائية هو جعلها تستدعي طريقة يتم تعريفها حسب الاصطلاح على أنها التخلص (التخلص المنطقي) بتمرير خطأ للإشارة إلى أن الاستدعاء قد تم إجراؤه بسبب الإنهاء بدلاً من استدعاء التخلص الصريح.
  • وهذا لأنه ليس من الآمن إجراء أية افتراضات حول الكائنات المُدارة الأخرى أثناء إنهاء أحد الكائنات (ربما تم الانتهاء منها بالفعل).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

هذه هي النسخة البسيطة، ولكن هناك الكثير من الفروق الدقيقة التي يمكن أن تعوقك عن هذا النمط.

  • يشير العقد الخاص بـ IDisposable.Dispose إلى أنه يجب أن يكون الاتصال عدة مرات آمنًا (يجب ألا يؤدي استدعاء Dispose على كائن تم التخلص منه بالفعل إلى أي شيء)
  • يمكن أن يكون الأمر معقدًا للغاية لإدارة التسلسل الهرمي لوراثة الكائنات التي يمكن التخلص منها بشكل صحيح، خاصة إذا كانت الطبقات المختلفة تقدم موارد جديدة يمكن التخلص منها وغير مُدارة.في النمط أعلاه، يعد Dispose(bool) افتراضيًا للسماح بتجاوزه بحيث يمكن إدارته، ولكني أجده عرضة للخطأ.

في رأيي، من الأفضل تجنب وجود أي أنواع تحتوي بشكل مباشر على كل من المراجع التي يمكن التخلص منها والموارد الأصلية التي قد تتطلب الانتهاء.توفر SafeHandles طريقة نظيفة للغاية للقيام بذلك عن طريق تغليف الموارد الأصلية في شكل يمكن التخلص منه والذي يوفر الإنهاء الخاص بها داخليًا (إلى جانب عدد من المزايا الأخرى مثل إزالة النافذة أثناء P/Invoc حيث يمكن فقدان المقبض الأصلي بسبب استثناء غير متزامن). .

إن تحديد SafeHandle ببساطة يجعل هذا أمرًا تافهًا:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

يسمح لك بتبسيط النوع الذي يحتوي على:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

أنا لا أعتقد ذلك.يمكنك التحكم في وقت استدعاء Dispose، مما يعني أنه يمكنك نظريًا كتابة كود التخلص الذي يضع افتراضات حول (على سبيل المثال) وجود كائنات أخرى.لا يمكنك التحكم في وقت استدعاء أداة الإنهاء، لذا سيكون من غير المناسب أن تقوم أداة الإنهاء تلقائيًا باستدعاء التخلص نيابةً عنك.


يحرر:ذهبت بعيدا واختبرت، فقط للتأكد:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

ليس في حالة تصفك ، لكن GC سوف تسمي اللمسات النهائية بالنسبة لك، إذا كان لديك واحدة.

لكن.مجموعة البيانات المهملة التالية، بدلاً من تجميعها، سينتقل الكائن إلى قائمة انتظار الإنهاء، ويتم جمع كل شيء، ثم يتم استدعاء أداة الإنهاء.سيتم تحرير المجموعة التالية بعد ذلك.

اعتمادًا على ضغط الذاكرة في تطبيقك، قد لا يكون لديك gc لإنشاء هذا الكائن لفترة من الوقت.لذا، في حالة دفق الملفات أو اتصال قاعدة البيانات، على سبيل المثال، قد تضطر إلى الانتظار لبعض الوقت حتى يتم تحرير المورد غير المُدار في استدعاء أداة الإنهاء لفترة من الوقت، مما يسبب بعض المشكلات.

لا، لا يسمى.

ولكن هذا يجعل من السهل عدم نسيان التخلص من الأشياء الخاصة بك.مجرد استخدام using الكلمة الرئيسية.

لقد قمت بالاختبار التالي لهذا:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

سوف GC لا التخلص من المكالمة.هو - هي يمكن اتصل بأداة الإنهاء الخاصة بك، ولكن حتى هذا غير مضمون في جميع الظروف.

انظر الى هذا شرط لمناقشة أفضل طريقة للتعامل مع هذا.

الوثائق على يمكن التخلص منه يقدم شرحًا واضحًا ومفصلاً للسلوك، بالإضافة إلى رمز المثال.لن يقوم GC باستدعاء Dispose() الطريقة على الواجهة، ولكنها ستستدعي أداة الإنهاء للكائن الخاص بك.

تم إنشاء نمط IDisposable بشكل أساسي ليتم استدعاؤه بواسطة المطور، إذا كان لديك كائن يقوم بتنفيذ IDispose، فيجب على المطور إما تنفيذ الأمر using الكلمة الأساسية حول سياق الكائن أو استدعاء الأسلوب Dispose مباشرة.

الحل الآمن للفشل للنمط هو تنفيذ أداة الإنهاء التي تستدعي طريقة Dispose().إذا لم تفعل ذلك، فقد تؤدي إلى حدوث بعض التسريبات في الذاكرة، مثل:إذا قمت بإنشاء بعض مجمّع COM ولم تقم مطلقًا باستدعاء System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (الذي سيتم وضعه في أسلوب التخلص).

لا يوجد سحر في clr لاستدعاء أساليب التخلص تلقائيًا بخلاف تتبع الكائنات التي تحتوي على أدوات الإنهاء وتخزينها في جدول Finalizer بواسطة GC واستدعائها عند بدء بعض الاستدلالات التنظيفية بواسطة GC.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top