C#:正确使用WeakReference IsAlive属性

时间:2016-11-23 19:26:49

标签: c# .net garbage-collection weak-references

正如here所述,如果WeakReference' s IsAlive返回true,则无法信任。现在,我试图了解correct way使用它:

不正确:

WeakReference dogRef = new WeakReference(dog);

// Later, try to ref original Dog

if (dogRef.IsAlive)
{
    // Oops - garbage collection on original Dog could occur here
    ((Dog)dogRef.Target).Bark();
}

正确:

WeakReference dogRef = new WeakReference(dog);

// Later, try to ref original Dog

Dog origDog = (Dog)dogRef.Target;
if (origDog != null)
{
    origDog.Bark();
}

我的问题是,在if(origDog != nulldogRef.Target != null之间GC的观点是否存在任何差异?假设,我不需要调用Dog类的方法,只需要检查目标是否存活。我是否应该始终将目标强制转换为类实例,还是可以将Target检查为null?

我问这个是因为如果对象处于活动状态并且不涉及对象本身,我想执行一些逻辑。

2 个答案:

答案 0 :(得分:4)

  

我的问题是,从GC的角度来看,在if(origDog!= null和dogRef.Target!= null?

之间是否存在任何差异?

使用origDog,如果origDog不为空(因为当dogRef.Target分配给它时确实存在引用,那么在写入之前它将继续不为空结束,或变得可收藏。

使用dogRef.Target != null然后问题不是那个电话 - 它会正常工作 - 但是时间介于两者之间,并尝试使用它。

(顺便提一下,虽然它打字更多,但通常在堆栈上创建一个临时值而不是两次击中属性通常会稍微提高效率。它的效率不是那么高在调用属性时值得做的是更自然地键入,但值得注意的是,如果不想创建临时dogRef的唯一原因是担心它对应用程序来说是额外的工作。) / p>

来自评论:

  

如果将Target与null进行比较,则会在范围结束前获得强引用

它没有,也没有分配。认识到范围与可收集性无关是很重要的。在代码中:

void SomeMethodWhichThereforeHasAScope()
{
  Dog origDog = (Dog)dogRef.Target;
  if (origDog != null)
  {
    Console.Write(dogRef.Target == null); // probably going to be false (though sometimes reads get reordered, so there's a chance that happens).
    origDog.Bark();
  }

  Console.Write(dogRef.Target == null); // could be true or false
  var sb = new StringBuilder("I'm a string that got referenced in a call to a method");
  Console.Write(sb.ToString());
  Console.Write(dogRef.Target == null); // even more likely to be true.
}

origDogTarget的第三次测试时属于范围,但在此之后不会使用。{p> Bark。这意味着对堆栈上的对象和/或用于调用WeakReference的寄存器的引用可能已被用于其他内容(更可能是方法中发生的工作越多),这意味着如果GC启动它可能找不到参考。

"适用范围"是关于可以使用变量的地方。 GC根据您 使用它的位置工作。一旦您停止使用它,GC可能会回收它引用的对象。通常我们不在乎,因为我们在使用它之后不会使用它(事实上),所以我们不会注意到。通过GC.KeepAlive()的另一个引用会改变它。

这就是WeakReference存在的原因。它实际上并没有做任何事情,它只是一种不会被优化的方法,所以如果你想要在范围内保留一个变量的唯一原因是一些不寻常的GC东西({{ 1}}适用于"不常见的GC内容")意味着您可能希望通过除该变量之外的其他内容使用相同的对象,它不会被收集直到{{1}之后打电话。

  

假设,我不需要调用Dog类的方法,只需要检查目标是否存活。我应该总是将目标强制转换为类实例,还是可以将Target检查为null?

检查它不是空的很好。确实使用KeepAlive()很好。 IsAlive的问题纯粹是在将来某个时候可能会成为IsAlive。任何检查生活的手段都是如此。

(我唯一一次见过Luciano Pavarotti他还活着。从那以后我就没见过他了。上次见到他时,他完全活着的事实并没有阻止他现在死了。false是完全相同)。

顺便提一下,对于单个呼叫,以下内容有效且方便:

WeakReference.IsAlive

因为((Dog)dogRef.Target)?.Bark(); 运算符将?.引用,所以它类似于:

dup

这样安全。

如果对象和您将要做的工作之间断开连接,您可以使用:

var temp = ((Dog)dogRef.Target)
if (temp != null)
  temp.Bark();

如上所述,var temp = dogRef.Target; if (temp != null) { DoStuffHere(); GC.KeepAlive(temp); // temp cannot be collected until this returns. } 只是一种无操作方法,不允许编译器和抖动进行优化。因此,必须在堆栈或寄存器中有一个引用传递给它,GC将看到它而不是收集它。

答案 1 :(得分:2)

<!-- in app/views/foo/bar.html.erb --> <%= form_tag "/dobaz" do %> <p>Reset: <input type="radio" name="reset" value="true">True</input> <input type="radio" name="reset" value="false">False</input> <input type="submit"/> <% end %> 的目的不是让代码知道什么时候使用IsAlive是安全的,而是检查一个引用是否已经消失,但没有任何可能让它不必要地保持活着。< / p>

如果代码想要使用WeakReference的目标,它必须将其复制到变量,然后检查它是否为空;如果赋值发生在引用之前,则存储在变量中的目标副本将使引用保持活动状态。使用WeakReference的危险在于,如果目标不存在引用其他引用,但上述代码就在GC周期即将开始时发生,GC可能会暂停收集对象(并使弱引用无效) )因为if (weakref.Target == null) handleObjectDeath();属性返回的临时引用。使用Target属性可以避免这种风险。