Search code examples
c#.netthread-safetyweak-references

Concurrent use of WeakReference<T>?


The documentation for WeakReference<T> has the common boilerplate for thread-safety:

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

However, I'm wondering whether it is indeed safe to use instance members of WeakReference<T> anyway?

In particular, I'm wondering whether is is safe to allow concurrent access to something like this:

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;
    }
}

Both TryGetTarget and SetTarget are used here, possibly invoked concurrently by multiple threads.

Both of them are invoking the private external Target property implemented by native code. (Reference source)

I'd like to know whether the native implementation really isn't safe for concurrent access before I start protecting code like the one above with synchronization locks.


What would count as safe to me here?

Simply, if I could use the code above without causing any unexpected exception or access violation due to concurrent invocations of the two methods.

More explicitly:

  • TryGetTarget would either return true and give me a non-null reference; or false. No exceptions.

  • SetTarget would not cause any exception.


Solution

  • By looking at the ecallist.h we can see that the internal methods of WeakTarget/WeakTarget<T> are implemented in the WeakReferenceNative/WeakReferenceOfTNative classes. Looking at their source code we can see that there is an AcquireWeakHandleSpinLock both in the SetTarget (used by the Target setter) and in the GetWeakReferenceTarget, used by the Target getter and by the TryGetTarget.

    So probably the whole Target/TryGetTarget are thread safe (in the sense that they won't corrupt internal structures of .NET), but you will need a better programmer than me to check that they really are :-)

    Clearly using the Target/TryGetTarget won't make the targetted object thread safe! That is another problem!