弱引用在调试和发布中的行为不同(未附加调试器).即使使用工厂方法

本文关键字:调试器 方法 工厂 调试 引用 布中 | 更新日期: 2023-09-27 18:19:06

[Doh!我是个白痴..我正在代码中扎根对象..]

我的代码在发布中按预期工作,但在调试中失败。

我有一个字典,其中包含其他对象的WeakReference实例。在 Release 中,字典将按预期"丢失"其值,一旦它们未被引用并发生收集。但是,在调试中,它似乎没有发生...

即使在调试中,我确实看到其他WeakReference被收集在调试中,但字典中的那些不是......

下面的代码显示了这一点。即使我在它们之间添加多个集合和延迟(Task.Delay(100)(,它仍然不会消失。

知道如何强制 WR 被取消吗?我不太介意,但我有一个测试,它会在调试中失败。

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            DoIt();
            Console.ReadLine();
        }
        private static async void DoIt()
        {
            string key = "k1";
            var dict = new WeakItemDictionary<string, string>();
            var s = dict.GetOrAdd(key, k => String.Concat("sdsdsd", "sdsdsdsdsd"));
            RunFullGCCollection();
            var found = dict.GetItemOrDefault(key);
            Console.WriteLine(found == null ? "Object got collected" : "Object is still alive");
        }
        private static void RunFullGCCollection()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }
    /// <summary>
    /// Creates a dictionary of weakly referenced object that will disapear when no longer in use.
    /// Be careful when adding functions to the class - you need to take a bunch of scenarios into account.
    /// See how GetOrAdd() works for more info.
    /// </summary>
    /// <typeparam name="K">Key of the dictionary</typeparam>
    /// <typeparam name="V">Value type for the dictionary</typeparam>
    public class WeakItemDictionary<K, V> where V : class
    {
        public const int CleanPassFrequency = 10;
        private Dictionary<K, WeakReference<V>> _dictionary = new Dictionary<K, WeakReference<V>>();
        private int _addCount = 0;
        public V GetOrAdd(K key, Func<K, V> factory)
        {
            WeakReference<V> weakRef;
            V value = null;
            if (!_dictionary.TryGetValue(key, out weakRef))
            {
                value = factory(key);
                weakRef = new WeakReference<V>(value);
                _dictionary[key] = weakRef;
                _addCount++;
            }
            // If the value is null, try to get it from the weak ref (to root it).
            if (value == null)
            {
                value = weakRef.GetTargetOrDefault();
                // If the value is still null, means the weak ref got cleaned. We need to recreate (again, rooted)
                if (value == null)
                {
                    value = factory(key);
                    weakRef.SetTarget(value);
                    _addCount++;
                }
            }
            CleanIfNeeded();
            return value;
        }
        public V GetItemOrDefault(K key)
        {
            WeakReference<V> weakRef;
            V value = null;
            if (_dictionary.TryGetValue(key, out weakRef))
            {
                value = weakRef.GetTargetOrDefault();
            }
            return value;
        }
        private void CleanIfNeeded()
        {
            Lazy<List<K>> keysToRemove = new Lazy<List<K>>(false);
            foreach (var item in _dictionary)
            {
                if (item.Value.IsDead())
                {
                    keysToRemove.Value.Add(item.Key);
                }
            }
            if (keysToRemove.IsValueCreated)
            {
                foreach (var item in keysToRemove.Value)
                {
                    _dictionary.Remove(item);
                }
            }
        }
    }
    public static class Extensions
    {
        public static bool IsDead<T>(this WeakReference<T> weak) where T : class
        {
            T t;
            bool result = !weak.TryGetTarget(out t);
            return result;
        }
        public static T GetTargetOrDefault<T>(this WeakReference<T> weak) where T : class
        {
            T t;
            bool result = !weak.TryGetTarget(out t);
            return t;
        }

    }
}

弱引用在调试和发布中的行为不同(未附加调试器).即使使用工厂方法

        var s = dict.GetOrAdd(key, k => String.Concat("sdsdsd", "sdsdsdsdsd"));

s变量具有对该对象的引用。 请注意,即使 DoIt(( 方法尚未完成执行并且 s 变量仍存储在方法的激活帧中,您也会强制集合。 当您在没有附加调试器的情况下运行发布版本时,这有效,它使垃圾回收器高效。 但调试时不会。 否则,首先存在发布配置的核心原因之一。

这篇文章详细解释了这种行为差异的技术原因。

这不是你应该担心的事情,你只需要了解为什么它的行为不同。 您可以通过在调用 GC 之前将s设置回 null 来更改结果。收集((。 或者移动字典。GetOrAdd(( 调用另一个方法。

根据MSDN:

弱引用允许垃圾回收器收集对象,同时仍允许应用程序访问对象。

许可证意味着:垃圾回收器可以收集对象,但不需要。因此,您永远不会确切知道何时收集它。即使在发布模式下运行它,有时 .NET 也不会收集它。

因此,即使您可以使用 @Hans Passant 的答案使其适用于这种特殊情况,您也永远无法确定它是否始终以相同的方式运行。它可以依赖于物理RAM和其他同时运行并消耗或多或少内存的程序。