Практическое использование System.WeakReference

Я понимаю, что делает System.WeakReference , но то, что я не могу понять, - это практический пример того, для чего он может быть полезен. Сам класс мне кажется, ну, хаком. Мне кажется, что есть другие, более эффективные средства решения проблемы, когда WeakReference используется в примерах, которые я видел. Каков канонический пример того, где действительно нужно использовать 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.

Есть две причины, по которым вы должны использовать WeakReference .

  1. Вместо глобальных объектов, объявленных как статические : глобальные объекты объявляются как статические поля, а статические поля не могут быть объединены в сборку мусора (сборщик мусора) до тех пор, пока они не будут объединены в сборку мусора 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 в самом конце, даже после того, как ThingsWrapperэто - то есть GC'ed, когда AppDomainGC'ed.

    Thing() staticThing
    Thing() privateThing
    ~Thing() privateThing
    ~ThingsWrapper
    ~Thing() staticThing
    

    Вместо этого мы можем обернуть Thingв WeakReference. Как и в wrStaticThingслучае с GC, нам понадобится метод с отложенной загрузкой, который я оставил для краткости.

    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.
    
  2. Для объектов, инициализация которых требует много времени : вы не хотите, чтобы объекты, на инициализацию которых требуется много времени, подвергались сборке мусора. Вы можете либо сохранить статическую ссылку, чтобы этого избежать (с минусами выше), либо использовать 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> чтобы миксин не перетаскивал объекты слишком долго.

Единственная проблема в том, что каждый раз, когда я выполняю доступ, я также проверяю мертвые ссылки и удаляю их. Не то чтобы они никого обидели, если, конечно, их не тысячи.