WeakReference 在 Debug 和 Release 中表现不同(没有附加调试器)。即使使用工厂方法
WeakReference behaving differently in Debug and Release (no debugger attached). Even when using a factory method
[呸!我是个白痴..我在代码中将对象植根..]
我的代码在 Release 中按预期工作,但在 Debug 中失败。
我有一个字典,其中包含 WeakReference
个其他对象的实例。在 Release 中,一旦未被引用并发生收集,字典就会像预期的那样“丢失”其值。然而,在Debug中,它似乎并没有发生......
即使在调试中,我确实看到其他 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;
}
}
}
根据MSDN:
A weak reference permits the garbage collector to collect the object while still allowing the application to access the object.
Permits表示:垃圾收集器可以收集对象,但不需要。所以你永远不会确切知道它何时被收集。即使您 运行 它处于 Release 模式,有时 .NET 也可能不收集它。
因此,即使您现在可以通过@Hans Passant 的回答使其适用于这种特殊情况,您也永远无法确定它是否会始终以相同的方式运行。它可以同时依赖于物理 RAM 和其他程序 运行ning 并消耗或多或少的内存。
var s = dict.GetOrAdd(key, k => String.Concat("sdsdsd", "sdsdsdsdsd"));
您的 s
变量引用了该对象。请注意,即使 DoIt() 方法尚未完成执行且 s
变量仍存储在该方法的激活帧中,您仍强制收集。当你 运行 没有附加调试器的发布版本时,这会起作用,它使垃圾收集器 高效 。但不是在调试时。否则,发布配置首先存在的核心原因之一。
这种行为差异的技术原因在 this post 中有详细解释。
这不是您应该担心的事情,您只需要了解为什么它的行为会有所不同。您可以通过在调用 GC.Collect() 之前将 s
设置回 null 来改变结果。或者将 dict.GetOrAdd() 调用移动到另一个方法中。
[呸!我是个白痴..我在代码中将对象植根..]
我的代码在 Release 中按预期工作,但在 Debug 中失败。
我有一个字典,其中包含 WeakReference
个其他对象的实例。在 Release 中,一旦未被引用并发生收集,字典就会像预期的那样“丢失”其值。然而,在Debug中,它似乎并没有发生......
即使在调试中,我确实看到其他 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;
}
}
}
根据MSDN:
A weak reference permits the garbage collector to collect the object while still allowing the application to access the object.
Permits表示:垃圾收集器可以收集对象,但不需要。所以你永远不会确切知道它何时被收集。即使您 运行 它处于 Release 模式,有时 .NET 也可能不收集它。
因此,即使您现在可以通过@Hans Passant 的回答使其适用于这种特殊情况,您也永远无法确定它是否会始终以相同的方式运行。它可以同时依赖于物理 RAM 和其他程序 运行ning 并消耗或多或少的内存。
var s = dict.GetOrAdd(key, k => String.Concat("sdsdsd", "sdsdsdsdsd"));
您的 s
变量引用了该对象。请注意,即使 DoIt() 方法尚未完成执行且 s
变量仍存储在该方法的激活帧中,您仍强制收集。当你 运行 没有附加调试器的发布版本时,这会起作用,它使垃圾收集器 高效 。但不是在调试时。否则,发布配置首先存在的核心原因之一。
这种行为差异的技术原因在 this post 中有详细解释。
这不是您应该担心的事情,您只需要了解为什么它的行为会有所不同。您可以通过在调用 GC.Collect() 之前将 s
设置回 null 来改变结果。或者将 dict.GetOrAdd() 调用移动到另一个方法中。