WeakReference<T>的并发使用?

4
WeakReference<T> 的文档中有一个关于线程安全的通用套话:
任何此类型的公共静态成员(在 Visual Basic 中为共享成员)都是线程安全的。但任何实例成员不能保证是线程安全的
但是,我想知道是否确实可以安全地使用 WeakReference<T> 的实例成员呢?
特别是,我想知道是否可以同时访问这样的内容是安全的:
class MyRef<T> where T : class
{
    private readonly Func<Task<T>> _fetcher;
    private readonly WeakReference<T> _wref;

    public MyRef(Func<Task<T>> fetcher, T target = null)
    {
        _fetcher = fetcher;
        _wref = new WeakReference<T>(target);
    }

    public async Task<T> GetTargetAsync()
    {
        T target;

        if (!_wref.TryGetTarget(out target))
        {
            target = await _fetcher();
            _wref.SetTarget(target);
        }

        return target;
    }
}

这里同时使用了 TryGetTargetSetTarget,它们可能会被多个线程并发地调用。

它们都调用了由本地代码实现的私有外部 Target 属性。(参考源码

在保护像上面那样的代码与同步锁之前,我想知道本地实现是否真的不安全以进行并发访问。


对我而言,什么是“安全”的?

简单来说,如果我可以使用上面的代码而不会因两种方法的并发调用导致任何意外异常或访问冲突,则为“安全”。

更明确地说:

  • TryGetTarget 要么返回一个非空引用的 true,要么返回 false。没有任何异常。

  • SetTarget 不会引起任何异常。


2
请定义您所说的“safe here”。“线程安全”不是一个普遍公认的概念。只有在它达到您期望的效果时才对来说是安全的。您希望它做什么或期望它做什么? - Lasse V. Karlsen
@LasseV.Karlsen 请查看我最近的编辑以获得澄清。 - Mårten Wikström
WeakReference<T> 的文档底部写道:“此类型的任何公共静态成员(在 Visual Basic 中为 Shared)都是线程安全的。任何实例成员都不能保证是线程安全的。(我强调) 因此,不能保证它不会崩溃。 - Lasse V. Karlsen
1
@LasseV.Karlsen 这是一个好的和有效的观点。然而,我仍然对细节感兴趣。为什么现在不安全,或者为什么现在安全? - Mårten Wikström
1
通过查看SetTarget在coreclr中的实现,它似乎受到AcquireWeakHandleSpinLock保护,这是一种锁。因此它很可能是线程安全的。其中“很可能”更像是“今晚我很可能吃肉”而不是“明天太阳还会升起”的意思 :-) - xanatos
显示剩余4条评论
1个回答

3
通过查看 ecallist.h,我们可以看到 WeakTarget/WeakTarget<T> 的内部方法是在 WeakReferenceNative/WeakReferenceOfTNative 类中实现的。查看它们的源代码,我们可以看到,在 SetTarget(由 Target setter 使用)和 GetWeakReferenceTarget 中都有一个 AcquireWeakHandleSpinLock,后者由 Target getter 和 TryGetTarget 使用。

所以,Target/TryGetTarget 应该是线程安全的 (在不破坏.NET内部结构的情况下),但你需要比我更好的程序员来确认它们真的是线程安全的 :-)

显然,使用 Target/TryGetTarget 并不会使目标对象变得线程安全!这是另一个问题!


请注意,此答案是针对.NET Core实现而非“旧版” .NET的情况,如果您仍在使用ASP.NET,请注意。 - Elliott Beach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接