Bläddra i källkod

Merge pull request #246 from akarnokd/LockFreeDisposableContainers

Lock free MultipleAssignmentDisposable
Brendan Forster 9 år sedan
förälder
incheckning
12134496ff

+ 37 - 29
Rx.NET/Source/System.Reactive.Core/Reactive/Disposables/MultipleAssignmentDisposable.cs

@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the Apache 2.0 License.
 // The .NET Foundation licenses this file to you under the Apache 2.0 License.
 // See the LICENSE file in the project root for more information. 
 // See the LICENSE file in the project root for more information. 
 
 
+using System.Threading;
+
 namespace System.Reactive.Disposables
 namespace System.Reactive.Disposables
 {
 {
     /// <summary>
     /// <summary>
@@ -9,7 +11,6 @@ namespace System.Reactive.Disposables
     /// </summary>
     /// </summary>
     public sealed class MultipleAssignmentDisposable : ICancelable
     public sealed class MultipleAssignmentDisposable : ICancelable
     {
     {
-        private readonly object _gate = new object();
         private IDisposable _current;
         private IDisposable _current;
 
 
         /// <summary>
         /// <summary>
@@ -26,13 +27,10 @@ namespace System.Reactive.Disposables
         {
         {
             get
             get
             {
             {
-                lock (_gate)
-                {
-                    // We use a sentinel value to indicate we've been disposed. This sentinel never leaks
-                    // to the outside world (see the Disposable property getter), so no-one can ever assign
-                    // this value to us manually.
-                    return _current == BooleanDisposable.True;
-                }
+                // We use a sentinel value to indicate we've been disposed. This sentinel never leaks
+                // to the outside world (see the Disposable property getter), so no-one can ever assign
+                // this value to us manually.
+                return Volatile.Read(ref _current) == BooleanDisposable.True;
             }
             }
         }
         }
 
 
@@ -44,28 +42,37 @@ namespace System.Reactive.Disposables
         {
         {
             get
             get
             {
             {
-                lock (_gate)
+                var a = Volatile.Read(ref _current);
+                // Don't leak the DISPOSED sentinel
+                if (a == BooleanDisposable.True)
                 {
                 {
-                    if (_current == BooleanDisposable.True /* see IsDisposed */)
-                        return DefaultDisposable.Instance; // Don't leak the sentinel value.
-
-                    return _current;
+                    a = DefaultDisposable.Instance;
                 }
                 }
+                return a;
             }
             }
 
 
             set
             set
             {
             {
-                var shouldDispose = false;
-                lock (_gate)
+                // Let's read the current value atomically (also prevents reordering).
+                var old = Volatile.Read(ref _current);
+                for (;;)
                 {
                 {
-                    shouldDispose = (_current == BooleanDisposable.True /* see IsDisposed */);
-                    if (!shouldDispose)
+                    // If it is the disposed instance, dispose the value.
+                    if (old == BooleanDisposable.True)
                     {
                     {
-                        _current = value;
+                        value?.Dispose();
+                        return;
                     }
                     }
+                    // Atomically swap in the new value and get back the old.
+                    var b = Interlocked.CompareExchange(ref _current, value, old);
+                    // If the old and new are the same, the swap was successful and we can quit
+                    if (old == b)
+                    {
+                        return;
+                    }
+                    // Otherwise, make the old reference the current and retry.
+                    old = b;
                 }
                 }
-                if (shouldDispose && value != null)
-                    value.Dispose();
             }
             }
         }
         }
 
 
@@ -74,19 +81,20 @@ namespace System.Reactive.Disposables
         /// </summary>
         /// </summary>
         public void Dispose()
         public void Dispose()
         {
         {
-            var old = default(IDisposable);
-
-            lock (_gate)
+            // Read the current atomically.
+            var a = Volatile.Read(ref _current);
+            // If it is the disposed instance, don't bother further.
+            if (a != BooleanDisposable.True)
             {
             {
-                if (_current != BooleanDisposable.True /* see IsDisposed */)
+                // Atomically swap in the disposed instance.
+                a = Interlocked.Exchange(ref _current, BooleanDisposable.True);
+                // It is possible there was a concurrent Dispose call so don't need to call Dispose()
+                // on DISPOSED
+                if (a != BooleanDisposable.True)
                 {
                 {
-                    old = _current;
-                    _current = BooleanDisposable.True /* see IsDisposed */;
+                    a?.Dispose();
                 }
                 }
             }
             }
-
-            if (old != null)
-                old.Dispose();
         }
         }
     }
     }
 }
 }