c# - WeakReference 在 Debug 和 Release 中表现不同(没有附加调试器)。即使使用工厂方法

标签 c# debugging clr weak-references

[呸!我是个白痴..我在代码中找到了对象..]

我的代码在 Release 中按预期工作,但在 Debug 中失败。

我有一个包含其他对象的 WeakReference 实例的字典。在 Release 中,一旦未被引用并发生收集,字典就会像预期的那样“丢失”其值。然而,在Debug中,它似乎并没有发生......

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

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

知道如何强制 WR 无效吗?我不太介意,但我有一个测试可以对此进行测试,它会在 Debug 中失败。

代码如下:

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 变量仍存储在该方法的激活帧中,您仍强制执行了一次收集。当您在没有附加调试器的情况下运行发布版本时,这会起作用,它使垃圾收集器高效。但不是在调试时。否则,发布配置首先存在的核心原因之一。

这种行为差异的技术原因在 this post 中有详细解释。 .

这不是您应该担心的事情,您只需要了解其行为不同的原因。您可以通过在调用 GC.Collect() 之前将 s 设置回 null 来改变结果。或者将 dict.GetOrAdd() 调用移动到另一个方法中。

关于c# - WeakReference 在 Debug 和 Release 中表现不同(没有附加调试器)。即使使用工厂方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33317625/

相关文章:

c# - 如果我的条件匹配,如何从另一个列表中选择项目?

c# - 我应该如何序列化包含大量对象列表的对象?

java - 调试问题我如何解决<终止,退出值: 0>C:\Program Files\Java\jdk1. 8.0_151\bin\javaw.exe(2020年3月4日下午12:02:48)

vb.net - 用VB.NET和COM Interop看似随机崩溃

c# - 在这种情况下,垃圾收集器会做什么?

.net - 防止方法出现在 Stack Traces 中

c# - .net 核心 API 中的无效模型类属性错误

c++ - 如何在 lldb 命令行中调用 C++ 对象的公共(public)函数?

visual-studio - 阻止 Visual Studio 尝试加载特定 DLL 的符号

c# - 使用 JSON.net 将枚举容器序列化为字符串