diff --git a/src/Avalonia.Base/Utilities/Ref.cs b/src/Avalonia.Base/Utilities/Ref.cs index eff26c602d..0b6abe5189 100644 --- a/src/Avalonia.Base/Utilities/Ref.cs +++ b/src/Avalonia.Base/Utilities/Ref.cs @@ -57,18 +57,18 @@ namespace Avalonia.Utilities public RefCounter(IDisposable item) { - _item = item; + _item = item ?? throw new ArgumentNullException(); _refs = 1; } - public void AddRef() + internal bool TryAddRef() { var old = _refs; while (true) { if (old == 0) { - throw new ObjectDisposedException("Cannot add a reference to a nonreferenced item"); + return false; } var current = Interlocked.CompareExchange(ref _refs, old + 1, old); if (current == old) @@ -77,6 +77,8 @@ namespace Avalonia.Utilities } old = current; } + + return true; } public void Release() @@ -101,12 +103,11 @@ namespace Avalonia.Utilities internal int RefCount => _refs; } - + class Ref : CriticalFinalizerObject, IRef where T : class { - private T? _item; - private readonly RefCounter _counter; - private readonly object _lock = new object(); + private volatile T? _item; + private volatile RefCounter? _counter; public Ref(T item, RefCounter counter) { @@ -114,65 +115,64 @@ namespace Avalonia.Utilities _counter = counter; } - public void Dispose() + public void Dispose() => Dispose(true); + void Dispose(bool disposing) { - lock (_lock) + var item = Interlocked.Exchange(ref _item, null); + + if (item != null) { - if (_item != null) - { - _counter.Release(); - _item = null; - } - GC.SuppressFinalize(this); + var counter = _counter!; + _counter = null; + if (disposing) + GC.SuppressFinalize(this); + counter.Release(); } } ~Ref() { - Dispose(); + Dispose(false); } + - public T Item - { - get - { - lock (_lock) - { - return _item!; - } - } - } + public T Item => _item ?? throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); public IRef Clone() { - lock (_lock) - { - if (_item != null) - { - var newRef = new Ref(_item, _counter); - _counter.AddRef(); - return newRef; - } + // Snapshot current ref state so we don't care if it's disposed in the meantime. + var counter = _counter; + var item = _item; + + // Check if ref was invalid + if (item == null || counter == null) throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); - } + + // Try to add a reference to the counter, if it fails, the item is disposed. + if (!counter.TryAddRef()) + throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); + + return new Ref(item, counter); } public IRef CloneAs() where TResult : class { - lock (_lock) - { - if (_item != null) - { - var castRef = new Ref((TResult)(object)_item, _counter); - Interlocked.MemoryBarrier(); - _counter.AddRef(); - return castRef; - } + // Snapshot current ref state so we don't care if it's disposed in the meantime. + var counter = _counter; + var item = (TResult?)(object?)_item; + + // Check if ref was invalid + if (item == null || counter == null) throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); - } + + // Try to add a reference to the counter, if it fails, the item is disposed. + if (!counter.TryAddRef()) + throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); + + return new Ref(item, counter); } - public int RefCount => _counter.RefCount; + public int RefCount => _counter?.RefCount ?? throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); } }