C# 如何安全地枚举列表<;Weakreference>;没有终结器的阻碍?
我的应用程序中有一个WeakReference的静态列表。在某个时刻,我想拍摄此列表中所有当前“活动”对象的快照 代码如下:C# 如何安全地枚举列表<;Weakreference>;没有终结器的阻碍?,c#,weak-references,C#,Weak References,我的应用程序中有一个WeakReference的静态列表。在某个时刻,我想拍摄此列表中所有当前“活动”对象的快照 代码如下: private static readonly List<WeakReference> myObjects = new List<WeakReference>(); public static MyObject[] CollectObjects() { var list = new List<MyObject>();
private static readonly List<WeakReference> myObjects = new List<WeakReference>();
public static MyObject[] CollectObjects()
{
var list = new List<MyObject>();
foreach (var item in myObjects)
{
if (!item.IsAlive)
continue;
var obj = item.Target as MyObject;
list.Add(obj);
}
return list.ToArray();
}
public class MyObject
{
private static readonly object _lockObject = new object();
WeakReference referenceToThis;
public MyObject()
{
lock (_lockObject)
{
referenceToThis = new WeakReference(this);
myObjects.Add(referenceToThis);
}
}
~MyObject()
{
lock (_lockObject)
{
myObjects.Remove(referenceToThis);
}
}
}
由于我的代码中没有其他内容涉及列表,因此我的假设是,垃圾收集器正在完成其中一些对象,就像我尝试枚举列表一样
我曾考虑在foreach循环周围添加一个锁(\u lockObject)
,但我不确定这样的锁会如何影响GC
是否有更好/正确/更安全的方法来枚举weakreference列表?这可能会损害GC和/或终结器(取决于性能),因为您在快速执行场景中使用的是
锁定
(关键部分)机制
您确实应该将枚举包装在同步块中,但也应该使用implement()模式(而不是锁
),这对于快速同步块非常有用:
readonly SpinLock sl = new SpinLock();
bool lockTaken = false;
...
try
{
sl.Enter(ref lockTaken);
// do your thing
}
finally
{
if (lockTaken)
{
sl.Exit();
}
}
终结器在整个垃圾收集机制中引入了很大的开销,因此如果可以,最好避免它。在您的情况下,您可以避免它并大大简化您的设计 不要让对象通过终结器检测自己的终结并将自己从该列表中删除,而是将
列表
替换为WeakCollection
WeakCollection
是一个集合,而不是列表。千万不要在收集足够的地方使用列表
WeakCollection
完全封装了它包含弱引用的事实,这意味着您将其用作常规列表,其接口中没有弱引用
WeakCollection
检测到弱引用已过期时,会自动从自身删除弱引用。(您会发现,将其实现为列表比将其实现为集合要复杂得多,所以再次强调,在集合足够的地方,不要使用列表。)
下面是一个WeakCollection
的示例实现。注意:我还没有测试过
namespace Whatever
{
using Sys = System;
using Legacy = System.Collections;
using Collections = System.Collections.Generic;
public class WeakCollection<T> : Collections.ICollection<T> where T : class
{
private readonly Collections.List<Sys.WeakReference<T>> list = new Collections.List<Sys.WeakReference<T>>();
public void Add( T item ) => list.Add( new Sys.WeakReference<T>( item ) );
public void Clear() => list.Clear();
public int Count => list.Count;
public bool IsReadOnly => false;
Legacy.IEnumerator Legacy.IEnumerable.GetEnumerator() => GetEnumerator();
public bool Contains( T item )
{
foreach( var element in this )
if( Equals( element, item ) )
return true;
return false;
}
public void CopyTo( T[] array, int arrayIndex )
{
foreach( var element in this )
array[arrayIndex++] = element;
}
public bool Remove( T item )
{
for( int i = 0; i < list.Count; i++ )
{
if( !list[i].TryGetTarget( out T target ) )
continue;
if( Equals( target, item ) )
{
list.RemoveAt( i );
return true;
}
}
return false;
}
public Collections.IEnumerator<T> GetEnumerator()
{
for( int i = list.Count - 1; i >= 0; i-- )
{
if( !list[i].TryGetTarget( out T element ) )
{
list.RemoveAt( i );
continue;
}
yield return element;
}
}
}
}
名称空间
{
使用Sys=系统;
使用Legacy=System.Collections;
使用集合=System.Collections.Generic;
公共类WeakCollection:Collections.i集合,其中T:class
{
私有只读集合。列表=新集合。列表();
公共作废新增(T项)=>list.Add(新增系统WeakReference(项));
public void Clear()=>list.Clear();
public int Count=>list.Count;
公共bool IsReadOnly=>false;
Legacy.IEnumerator Legacy.IEnumerable.GetEnumerator()=>GetEnumerator();
公共布尔包含(T项)
{
foreach(本文件中的var元素)
if(等于(元素、项目))
返回true;
返回false;
}
public void CopyTo(T[]数组,int arrayIndex)
{
foreach(本文件中的var元素)
数组[arrayIndex++]=元素;
}
公共布尔删除(T项)
{
for(int i=0;i=0;i--)
{
if(!list[i].TryGetTarget(out T元素))
{
列表。删除(i);
继续;
}
收益-收益要素;
}
}
}
}
是否尝试将true
作为第二个参数传递给WeakReference
?还有一个可以帮助的现有参数不,我必须承认我不理解这个参数的作用。~MyObject()
在C#中,你不知道什么时候调用它,如果myObjects
引用你的对象,它就不能为GC释放,所以可能~MyObject()
从未工作过?没有在foreach循环中使用锁是一个明显的错误,您必须修复它。请注意,匆忙销毁弱引用通常是不明智的。例如,您也可以在迭代集合时执行此操作,但需要的是for而不是foreach。锁定插入和移除而不是foreach显然对您的问题没有影响。但我不确定在这里使用lock是不是一个好主意:如果你手动检查“有效性”,为什么还要麻烦自动从列表中删除死物呢,我不确定将维护逻辑放在GetEnumerator中并让GetEnumerator以相反的顺序返回列表对我来说有点奇怪。这是一个收藏。因此,它是无序的。因此,可以按任何顺序返回项目。但如果你真的无法忍受,你可以改变它,它只会稍微复杂一点。至于枚举器中的维护逻辑,缓存就是这样工作的,这本质上就是一个缓存。我明白你的意思。我认为这是一个更好的方法,再次感谢分享!