Я понимаю, что делает System.WeakReference, но что мне не кажется «понять» - это практический пример того, для чего это может быть полезно. Сам класс мне кажется, ну, хаком. Мне кажется, что есть другие, более эффективные средства решения проблемы, когда WeakReference используется в примерах, которые я видел. Каков канонический пример того, где действительно нужно использовать WeakReference? Разве мы не пытаемся уйти дальше от этого типа поведения и использования этого класса?
Практическое использование System.WeakReference
Ответы (4)
Один из полезных примеров - это разработчики объектно-ориентированной базы данных DB4O. Там WeakReferences используются как своего рода световой кеш: он будет хранить ваши объекты в памяти только до тех пор, пока это делает ваше приложение, что позволяет вам размещать реальный кеш поверх.
Другое применение - реализация слабых обработчиков событий. В настоящее время одним из основных источников утечек памяти в приложениях .NET является забвение удаления обработчиков событий. Например.
public MyForm()
{
MyApplication.Foo += someHandler;
}
Видите проблему? В приведенном выше фрагменте MyForm будет храниться в памяти навсегда, пока MyApplication существует в памяти. Создайте 10 MyForms, закройте их все, ваши 10 MyForms все еще будут в памяти и будут поддерживаться обработчиком событий.
Введите WeakReference. Вы можете создать слабый обработчик событий, используя 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.
Вместо глобальных объектов, объявленных как статические: глобальные объекты объявляются как статические поля, а статические поля не могут быть объединены в сборку мусора (сборщик мусора), пока
AppDomainне будет объединен в сборку мусора. Таким образом, вы рискуете исключить нехватку памяти. Вместо этого мы можем заключить глобальный объект вWeakReference. Несмотря на то, что самWeakReferenceобъявлен статическим, объект, на который он указывает, будет обработан сборщиком мусора при нехватке памяти.Обычно используйте
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); } }Примечание из вывода ниже
staticThingGC'ed в самом конце даже после того, какThingsWrapperis - т.е. GC'ed, когдаAppDomainGC'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вызывается сборщиком мусора при вызове потока сборщика мусора.Thing() wrStaticThing ~Thing() wrStaticThing ~WeakReferenceThing WeakReference is dead.Для объектов, инициализация которых требует много времени: вы не хотите, чтобы объекты, инициализация которых требует много времени, подвергались сборке мусора. Вы можете сохранить статическую ссылку, чтобы избежать этого (с минусами из вышеупомянутого пункта), или использовать
WeakReference.