الاستخدام العملي لـ System.WeakReference
-
08-06-2019 - |
سؤال
أنا أفهم ماذا System.WeakReference يفعل ذلك، ولكن ما لا أستطيع فهمه هو مثال عملي لما قد يكون مفيدًا له.يبدو لي أن الفصل نفسه عبارة عن اختراق.يبدو لي أن هناك وسائل أخرى أفضل لحل المشكلة حيث يتم استخدام WeakReference في الأمثلة التي رأيتها.ما هو المثال الأساسي للمكان الذي يتعين عليك فيه استخدام WeakReference؟ألسنا نحاول الحصول على أبعد بعيدا عن هذا النوع من السلوك واستخدام هذا الفصل؟
المحلول
أحد الأمثلة المفيدة هو الأشخاص الذين يقومون بتشغيل قاعدة بيانات DB4O الموجهة للكائنات.هناك، يتم استخدام WeakReferences كنوع من ذاكرة التخزين المؤقت الخفيفة:سوف يحتفظ بالأشياء الخاصة بك في الذاكرة فقط طالما أن التطبيق الخاص بك يفعل ذلك، مما يسمح لك بوضع ذاكرة تخزين مؤقت حقيقية في الأعلى.
سيكون الاستخدام الآخر هو تنفيذ معالجات الأحداث الضعيفة.حاليًا، أحد المصادر الكبيرة لتسرب الذاكرة في تطبيقات .NET هو نسيان إزالة معالجات الأحداث.على سبيل المثال
public MyForm()
{
MyApplication.Foo += someHandler;
}
ترى المشكلة؟في المقتطف أعلاه، سيظل MyForm حيًا في الذاكرة إلى الأبد طالما أن MyApplication حي في الذاكرة.قم بإنشاء 10 MyForms، وأغلقها جميعًا، وستظل الـ 10 MyForms الخاصة بك في الذاكرة، ويتم الاحتفاظ بها على قيد الحياة بواسطة معالج الحدث.
أدخل مرجع ضعيف.يمكنك إنشاء معالج أحداث ضعيف باستخدام WeakReferences بحيث يكون someHandler معالج أحداث ضعيفًا لـ MyApplication.Foo، وبالتالي إصلاح تسرب الذاكرة لديك!
هذه ليست مجرد نظرية.نشر داستن كامبل من مدونة DidItWith.NET تنفيذ معالجات الأحداث الضعيفة باستخدام System.WeakReference.
نصائح أخرى
أستخدمه لتنفيذ ذاكرة تخزين مؤقت حيث يتم جمع البيانات غير المستخدمة تلقائيًا:
class Cache<TKey,TValue> : IEnumerable<KeyValuePair<TKey,TValue>>
{ Dictionary<TKey,WeakReference> dict = new Dictionary<TKey,WeakReference>();
public TValue this[TKey key]
{ get {lock(dict){ return getInternal(key);}}
set {lock(dict){ setInteral(key,value);}}
}
void setInteral(TKey key, TValue val)
{ if (dict.ContainsKey(key)) dict[key].Target = val;
else dict.Add(key,new WeakReference(val));
}
public void Clear() { dict.Clear(); }
/// <summary>Removes any dead weak references</summary>
/// <returns>The number of cleaned-up weak references</returns>
public int CleanUp()
{ List<TKey> toRemove = new List<TKey>(dict.Count);
foreach(KeyValuePair<TKey,WeakReference> kv in dict)
{ if (!kv.Value.IsAlive) toRemove.Add(kv.Key);
}
foreach (TKey k in toRemove) dict.Remove(k);
return toRemove.Count;
}
public bool Contains(string key)
{ lock (dict) { return containsInternal(key); }
}
bool containsInternal(TKey key)
{ return (dict.ContainsKey(key) && dict[key].IsAlive);
}
public bool Exists(Predicate<TValue> match)
{ if (match==null) throw new ArgumentNullException("match");
lock (dict)
{ foreach (WeakReference weakref in dict.Values)
{ if ( weakref.IsAlive
&& match((TValue) weakref.Target)) return true;
}
}
return false;
}
/* ... */
}
أستخدم مرجعًا ضعيفًا لحفظ الحالة في الخلطات.تذكر أن الخلطات ثابتة، لذا عندما تستخدم كائنًا ثابتًا لربط الحالة بكائن غير ثابت، فلن تعرف أبدًا المدة التي ستستغرقها.لذلك بدلاً من الاحتفاظ بـ Dictionary<myobject, myvalue>
أحتفظ ب Dictionary<WeakReference,myvalue>
لمنع المزيج من سحب الأشياء لفترة طويلة.
المشكلة الوحيدة هي أنه في كل مرة أقوم فيها بالوصول، أقوم أيضًا بالتحقق من المراجع الميتة وإزالتها.لا يعني ذلك أنهم يؤذون أحداً، إلا إذا كان هناك الآلاف بالطبع.
هناك سببان وراء استخدامك WeakReference
.
بدلاً من الكائنات العالمية المعلنة على أنها ثابتة:يتم تعريف الكائنات العامة كحقول ثابتة ولا يمكن GC'ed للحقول الثابتة (تجميع البيانات المهملة) حتى
AppDomain
هو GC'ed.لذلك فإنك تخاطر باستثناءات خارج الذاكرة.بدلاً من ذلك، يمكننا تغليف الكائن العمومي في ملفWeakReference
.على الرغم من أنWeakReference
تم إعلان نفسه ثابتًا، وسيتم GC'ed الكائن الذي يشير إليه عندما تكون الذاكرة منخفضة.في الأساس، استخدم
wrStaticObject
بدلاً منstaticObject
.class ThingsWrapper { //private static object staticObject = new object(); private static WeakReference wrStaticObject = new WeakReference(new object()); }
تطبيق بسيط لإثبات أن الكائن الثابت يتم تجميعه من القمامة عندما يكون AppDomain كذلك.
class StaticGarbageTest { public static void Main1() { var s = new ThingsWrapper(); s = null; GC.Collect(); GC.WaitForPendingFinalizers(); } } class ThingsWrapper { private static Thing staticThing = new Thing("staticThing"); private Thing privateThing = new Thing("privateThing"); ~ThingsWrapper() { Console.WriteLine("~ThingsWrapper"); } } class Thing { protected string name; public Thing(string name) { this.name = name; Console.WriteLine("Thing() " + name); } public override string ToString() { return name; } ~Thing() { Console.WriteLine("~Thing() " + name); } }
لاحظ من الإخراج أدناه
staticThing
يتم GC'ed في النهاية حتى بعد ذلكThingsWrapper
هو - أي.GC'ed متىAppDomain
هو GC'ed.Thing() staticThing Thing() privateThing ~Thing() privateThing ~ThingsWrapper ~Thing() staticThing
بدلا من ذلك يمكننا التفاف
Thing
فيWeakReference
.مثلwrStaticThing
يمكن أن يكون GC'ed، فسنحتاج إلى طريقة محملة بالبطء والتي تركتها للإيجاز.class WeakReferenceTest { public static void Main1() { var s = new WeakReferenceThing(); s = null; GC.Collect(); GC.WaitForPendingFinalizers(); if (WeakReferenceThing.wrStaticThing.IsAlive) Console.WriteLine("WeakReference: {0}", (Thing)WeakReferenceThing.wrStaticThing.Target); else Console.WriteLine("WeakReference is dead."); } } class WeakReferenceThing { public static WeakReference wrStaticThing; static WeakReferenceThing() { wrStaticThing = new WeakReference(new Thing("wrStaticThing")); } ~WeakReferenceThing() { Console.WriteLine("~WeakReferenceThing"); } //lazy-loaded method to new Thing }
لاحظ من الإخراج أدناه ذلك
wrStaticThing
يتم GC'ed عند استدعاء مؤشر ترابط GC.Thing() wrStaticThing ~Thing() wrStaticThing ~WeakReferenceThing WeakReference is dead.
بالنسبة للكائنات التي تستغرق وقتًا طويلاً في التهيئة:لا تريد أن يتم GC'ed للكائنات التي تستغرق وقتًا طويلاً في البدء.يمكنك إما الاحتفاظ بمرجع ثابت لتجنب ذلك (مع سلبيات النقطة أعلاه) أو استخدامه
WeakReference
.