【问题标题】:Practical use of System.WeakReferenceSystem.WeakReference 的实际使用
【发布时间】:2008-08-19 02:45:27
【问题描述】:

我了解System.WeakReference 的作用,但我似乎无法理解它可能有用的实际示例。在我看来,这门课本身就是一个 hack。在我看来,还有其他更好的方法可以解决在我见过的示例中使用 Wea​​kReference 的问题。什么是您真正必须使用 Wea​​kReference 的典型示例?我们不是试图让这种行为和使用这个类更远吗?

【问题讨论】:

    标签: .net garbage-collection


    【解决方案1】:

    一个有用的例子是运行 DB4O 面向对象数据库的人。在那里,WeakReferences 被用作一种轻型缓存:它只会将您的对象保留在内存中,只要您的应用程序这样做,您就可以在顶部放置一个真正的缓存。

    另一个用途是实现弱事件处理程序。目前,.NET 应用程序中内存泄漏的一大来源是忘记删除事件处理程序。例如

    public MyForm()
    {
        MyApplication.Foo += someHandler;
    }
    

    看到问题了吗?在上面的 sn-p 中,只要 MyApplication 在内存中存在,MyForm 就会在内存中永远保持活跃。创建 10 个 MyForms,将它们全部关闭,您的 10 个 MyForms 仍将保留在内存中,由事件处理程序保持活动状态。

    输入弱引用。您可以使用 Wea​​kReferences 构建弱事件处理程序,以便 someHandler 成为 MyApplication.Foo 的弱事件处理程序,从而修复您的内存泄漏!

    这不仅仅是理论。 DidItWith.NET 博客的 Dustin Campbell 使用 System.WeakReference 发布了an implementation of weak event handlers

    【讨论】:

    • 是的,它非常漂亮。我们在工作中采用了他的代码,并添加了一些处理其他类型的事件处理程序(例如非通用事件处理程序、PropertyChangedEventHandler 等)。对我们来说效果很好。
    • 为实现弱事件处理程序提供的链接现在似乎已失效。有人有其他链接可以推荐吗?
    【解决方案2】:

    我用它来实现一个缓存,其中未使用的条目会自动被垃圾收集:

    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;
         }
    
        /* ... */
       }
    

    【讨论】:

      【解决方案3】:

      我在 mixins 中使用弱引用来保持状态。请记住,mixin 是静态的,因此当您使用静态对象将状态附加到非静态对象时,您永远不知道需要多长时间。所以我没有保留Dictionary&lt;myobject, myvalue&gt;,而是保留了Dictionary&lt;WeakReference,myvalue&gt;,以防止mixin拖得太久。

      唯一的问题是,每次访问时,我都会检查死引用并删除它们。并不是说他们伤害了任何人,除非有成千上万,当然。

      【讨论】:

      • 从 .NET 4.0 开始,ConditionalWeakTable 可能更适合这种用法。
      • 特别是,CWT 会自动覆盖“[remember to] check for dead references and remove them”的最后一个注释,因为 Dictionarynot 允许值在条目被实际删除之前被回收。
      【解决方案4】:

      您使用WeakReference 的原因有两个。

      1. 而不是声明为静态的全局对象:全局对象被声明为静态字段,并且静态字段在AppDomain 被 GC 之前不能被 GC(垃圾收集)。所以你冒着内存不足异常的风险。相反,我们可以将全局对象包装在 WeakReference 中。即使WeakReference 本身被声明为静态的,它指向的对象也会在内存不足时被 GC 处理。

        基本上,使用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 下面的输出即使在 ThingsWrapper 被 GC 之后也是在最后被 GC - 即当 AppDomain 被 GC 时被 GC。

        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
        }
        

        请注意,在调用 GC 线程时,wrStaticThing 会被 GC 处理。

        Thing() wrStaticThing
        ~Thing() wrStaticThing
        ~WeakReferenceThing
        WeakReference is dead.
        
      2. 对于初始化耗时的对象:您不希望对初始化耗时的对象进行 GC。您可以保留一个静态引用来避免这种情况(从上述观点来看有缺点)或使用WeakReference

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-21
        • 2011-10-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-10
        相关资源
        最近更新 更多