Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,346 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reactive.Disposables;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Reactive.Concurrency;
namespace System.Reactive.Subjects
{
/// <summary>
/// Represents the result of an asynchronous operation.
/// The last value before the OnCompleted notification, or the error received through OnError, is sent to all subscribed observers.
/// </summary>
/// <typeparam name="T">The type of the elements processed by the subject.</typeparam>
public sealed class AsyncSubject<T> : ISubject<T>, IDisposable
#if HAS_AWAIT
, INotifyCompletion
#endif
{
private readonly object _gate = new object();
private ImmutableList<IObserver<T>> _observers;
private bool _isDisposed;
private bool _isStopped;
private T _value;
private bool _hasValue;
private Exception _exception;
/// <summary>
/// Creates a subject that can only receive one value and that value is cached for all future observations.
/// </summary>
public AsyncSubject()
{
_observers = new ImmutableList<IObserver<T>>();
}
/// <summary>
/// Indicates whether the subject has observers subscribed to it.
/// </summary>
public bool HasObservers
{
get
{
var observers = _observers;
return observers != null && observers.Data.Length > 0;
}
}
/// <summary>
/// Notifies all subscribed observers about the end of the sequence, also causing the last received value to be sent out (if any).
/// </summary>
public void OnCompleted()
{
var os = default(IObserver<T>[]);
var v = default(T);
var hv = false;
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
os = _observers.Data;
_observers = new ImmutableList<IObserver<T>>();
_isStopped = true;
v = _value;
hv = _hasValue;
}
}
if (os != null)
{
if (hv)
{
foreach (var o in os)
{
o.OnNext(v);
o.OnCompleted();
}
}
else
foreach (var o in os)
o.OnCompleted();
}
}
/// <summary>
/// Notifies all subscribed observers about the exception.
/// </summary>
/// <param name="error">The exception to send to all observers.</param>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
var os = default(IObserver<T>[]);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
os = _observers.Data;
_observers = new ImmutableList<IObserver<T>>();
_isStopped = true;
_exception = error;
}
}
if (os != null)
foreach (var o in os)
o.OnError(error);
}
/// <summary>
/// Sends a value to the subject. The last value received before successful termination will be sent to all subscribed and future observers.
/// </summary>
/// <param name="value">The value to store in the subject.</param>
public void OnNext(T value)
{
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
_value = value;
_hasValue = true;
}
}
}
/// <summary>
/// Subscribes an observer to the subject.
/// </summary>
/// <param name="observer">Observer to subscribe to the subject.</param>
/// <returns>Disposable object that can be used to unsubscribe the observer from the subject.</returns>
/// <exception cref="ArgumentNullException"><paramref name="observer"/> is null.</exception>
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
var ex = default(Exception);
var v = default(T);
var hv = false;
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
_observers = _observers.Add(observer);
return new Subscription(this, observer);
}
ex = _exception;
hv = _hasValue;
v = _value;
}
if (ex != null)
observer.OnError(ex);
else if (hv)
{
observer.OnNext(v);
observer.OnCompleted();
}
else
observer.OnCompleted();
return Disposable.Empty;
}
class Subscription : IDisposable
{
private readonly AsyncSubject<T> _subject;
private IObserver<T> _observer;
public Subscription(AsyncSubject<T> subject, IObserver<T> observer)
{
_subject = subject;
_observer = observer;
}
public void Dispose()
{
if (_observer != null)
{
lock (_subject._gate)
{
if (!_subject._isDisposed && _observer != null)
{
_subject._observers = _subject._observers.Remove(_observer);
_observer = null;
}
}
}
}
}
void CheckDisposed()
{
if (_isDisposed)
throw new ObjectDisposedException(string.Empty);
}
/// <summary>
/// Unsubscribe all observers and release resources.
/// </summary>
public void Dispose()
{
lock (_gate)
{
_isDisposed = true;
_observers = null;
_exception = null;
_value = default(T);
}
}
#if HAS_AWAIT
/// <summary>
/// Gets an awaitable object for the current AsyncSubject.
/// </summary>
/// <returns>Object that can be awaited.</returns>
public AsyncSubject<T> GetAwaiter()
{
return this;
}
/// <summary>
/// Specifies a callback action that will be invoked when the subject completes.
/// </summary>
/// <param name="continuation">Callback action that will be invoked when the subject completes.</param>
/// <exception cref="ArgumentNullException"><paramref name="continuation"/> is null.</exception>
public void OnCompleted(Action continuation)
{
if (continuation == null)
throw new ArgumentNullException("continuation");
OnCompleted(continuation, true);
}
#endif
private void OnCompleted(Action continuation, bool originalContext)
{
//
// [OK] Use of unsafe Subscribe: this type's Subscribe implementation is safe.
//
this.Subscribe/*Unsafe*/(new AwaitObserver(continuation, originalContext));
}
class AwaitObserver : IObserver<T>
{
#if HAS_AWAIT
private readonly SynchronizationContext _context;
#endif
private readonly Action _callback;
public AwaitObserver(Action callback, bool originalContext)
{
#if HAS_AWAIT
if (originalContext)
_context = SynchronizationContext.Current;
#else
System.Diagnostics.Debug.Assert(!originalContext);
#endif
_callback = callback;
}
public void OnCompleted()
{
InvokeOnOriginalContext();
}
public void OnError(Exception error)
{
InvokeOnOriginalContext();
}
public void OnNext(T value)
{
}
private void InvokeOnOriginalContext()
{
#if HAS_AWAIT
if (_context != null)
{
//
// No need for OperationStarted and OperationCompleted calls here;
// this code is invoked through await support and will have a way
// to observe its start/complete behavior, either through returned
// Task objects or the async method builder's interaction with the
// SynchronizationContext object.
//
_context.Post(c => ((Action)c)(), _callback);
}
else
#endif
{
_callback();
}
}
}
/// <summary>
/// Gets whether the AsyncSubject has completed.
/// </summary>
public bool IsCompleted
{
get
{
return _isStopped;
}
}
/// <summary>
/// Gets the last element of the subject, potentially blocking until the subject completes successfully or exceptionally.
/// </summary>
/// <returns>The last element of the subject. Throws an InvalidOperationException if no element was received.</returns>
/// <exception cref="InvalidOperationException">The source sequence is empty.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Await pattern for C# and VB compilers.")]
public T GetResult()
{
if (!_isStopped)
{
var e = new ManualResetEvent(false);
OnCompleted(() => e.Set(), false);
e.WaitOne();
}
_exception.ThrowIfNotNull();
if (!_hasValue)
throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS);
return _value;
}
}
}

View File

@@ -0,0 +1,237 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reactive.Disposables;
namespace System.Reactive.Subjects
{
/// <summary>
/// Represents a value that changes over time.
/// Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications.
/// </summary>
/// <typeparam name="T">The type of the elements processed by the subject.</typeparam>
public sealed class BehaviorSubject<T> : ISubject<T>, IDisposable
{
/// <summary>
/// Gets the current value or throws an exception.
/// </summary>
/// <value>The initial value passed to the constructor until <see cref="OnNext"/> is called; after which, the last value passed to <see cref="OnNext"/>.</value>
/// <remarks>
/// <para><see cref="Value"/> is frozen after <see cref="OnCompleted"/> is called.</para>
/// <para>After <see cref="OnError"/> is called, <see cref="Value"/> always throws the specified exception.</para>
/// <para>An exception is always thrown after <see cref="Dispose"/> is called.</para>
/// <alert type="caller">
/// Reading <see cref="Value"/> is a thread-safe operation, though there's a potential race condition when <see cref="OnNext"/> or <see cref="OnError"/> are being invoked concurrently.
/// In some cases, it may be necessary for a caller to use external synchronization to avoid race conditions.
/// </alert>
/// </remarks>
/// <exception cref="ObjectDisposedException">Dispose was called.</exception>
public T Value
{
get
{
lock (_gate)
{
CheckDisposed();
if (_exception != null)
{
throw _exception;
}
return _value;
}
}
}
private readonly object _gate = new object();
private ImmutableList<IObserver<T>> _observers;
private bool _isStopped;
private T _value;
private Exception _exception;
private bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.BehaviorSubject&lt;T&gt;"/> class which creates a subject that caches its last value and starts with the specified value.
/// </summary>
/// <param name="value">Initial value sent to observers when no other value has been received by the subject yet.</param>
public BehaviorSubject(T value)
{
_value = value;
_observers = new ImmutableList<IObserver<T>>();
}
/// <summary>
/// Indicates whether the subject has observers subscribed to it.
/// </summary>
public bool HasObservers
{
get
{
var observers = _observers;
return observers != null && observers.Data.Length > 0;
}
}
/// <summary>
/// Notifies all subscribed observers about the end of the sequence.
/// </summary>
public void OnCompleted()
{
var os = default(IObserver<T>[]);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
os = _observers.Data;
_observers = new ImmutableList<IObserver<T>>();
_isStopped = true;
}
}
if (os != null)
{
foreach (var o in os)
o.OnCompleted();
}
}
/// <summary>
/// Notifies all subscribed observers about the exception.
/// </summary>
/// <param name="error">The exception to send to all observers.</param>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
var os = default(IObserver<T>[]);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
os = _observers.Data;
_observers = new ImmutableList<IObserver<T>>();
_isStopped = true;
_exception = error;
}
}
if (os != null)
foreach (var o in os)
o.OnError(error);
}
/// <summary>
/// Notifies all subscribed observers about the arrival of the specified element in the sequence.
/// </summary>
/// <param name="value">The value to send to all observers.</param>
public void OnNext(T value)
{
var os = default(IObserver<T>[]);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
_value = value;
os = _observers.Data;
}
}
if (os != null)
{
foreach (var o in os)
o.OnNext(value);
}
}
/// <summary>
/// Subscribes an observer to the subject.
/// </summary>
/// <param name="observer">Observer to subscribe to the subject.</param>
/// <returns>Disposable object that can be used to unsubscribe the observer from the subject.</returns>
/// <exception cref="ArgumentNullException"><paramref name="observer"/> is null.</exception>
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
var ex = default(Exception);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
_observers = _observers.Add(observer);
observer.OnNext(_value);
return new Subscription(this, observer);
}
ex = _exception;
}
if (ex != null)
observer.OnError(ex);
else
observer.OnCompleted();
return Disposable.Empty;
}
class Subscription : IDisposable
{
private readonly BehaviorSubject<T> _subject;
private IObserver<T> _observer;
public Subscription(BehaviorSubject<T> subject, IObserver<T> observer)
{
_subject = subject;
_observer = observer;
}
public void Dispose()
{
if (_observer != null)
{
lock (_subject._gate)
{
if (!_subject._isDisposed && _observer != null)
{
_subject._observers = _subject._observers.Remove(_observer);
_observer = null;
}
}
}
}
}
void CheckDisposed()
{
if (_isDisposed)
throw new ObjectDisposedException(string.Empty);
}
/// <summary>
/// Unsubscribe all observers and release resources.
/// </summary>
public void Dispose()
{
lock (_gate)
{
_isDisposed = true;
_observers = null;
_value = default(T);
_exception = null;
}
}
}
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reactive.Disposables;
using System.Reactive.Linq;
namespace System.Reactive.Subjects
{
/// <summary>
/// Represents an observable wrapper that can be connected and disconnected from its underlying observable sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
/// <typeparam name="TResult">The type of the elements in the resulting sequence, after transformation through the subject.</typeparam>
internal class ConnectableObservable<TSource, TResult> : IConnectableObservable<TResult>
{
private readonly ISubject<TSource, TResult> _subject;
private readonly IObservable<TSource> _source;
private readonly object _gate;
private Connection _connection;
/// <summary>
/// Creates an observable that can be connected and disconnected from its source.
/// </summary>
/// <param name="source">Underlying observable source sequence that can be connected and disconnected from the wrapper.</param>
/// <param name="subject">Subject exposed by the connectable observable, receiving data from the underlying source sequence upon connection.</param>
public ConnectableObservable(IObservable<TSource> source, ISubject<TSource, TResult> subject)
{
_subject = subject;
_source = source.AsObservable(); // This gets us auto-detach behavior; otherwise, we'd have to roll our own, including trampoline installation.
_gate = new object();
}
/// <summary>
/// Connects the observable wrapper to its source. All subscribed observers will receive values from the underlying observable sequence as long as the connection is established.
/// </summary>
/// <returns>Disposable object used to disconnect the observable wrapper from its source, causing subscribed observer to stop receiving values from the underlying observable sequence.</returns>
public IDisposable Connect()
{
lock (_gate)
{
if (_connection == null)
{
var subscription = _source.SubscribeSafe(_subject);
_connection = new Connection(this, subscription);
}
return _connection;
}
}
class Connection : IDisposable
{
private readonly ConnectableObservable<TSource, TResult> _parent;
private IDisposable _subscription;
public Connection(ConnectableObservable<TSource, TResult> parent, IDisposable subscription)
{
_parent = parent;
_subscription = subscription;
}
public void Dispose()
{
lock (_parent._gate)
{
if (_subscription != null)
{
_subscription.Dispose();
_subscription = null;
_parent._connection = null;
}
}
}
}
/// <summary>
/// Subscribes an observer to the observable sequence. No values from the underlying observable source will be received unless a connection was established through the Connect method.
/// </summary>
/// <param name="observer">Observer that will receive values from the underlying observable source when the current ConnectableObservable instance is connected through a call to Connect.</param>
/// <returns>Disposable used to unsubscribe from the observable sequence.</returns>
public IDisposable Subscribe(IObserver<TResult> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
return _subject.SubscribeSafe(observer);
}
}
}

View File

@@ -0,0 +1,353 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reactive.Concurrency;
namespace System.Reactive.Subjects
{
/// <summary>
/// Represents an object that is both an observable sequence as well as an observer.
/// Each notification is broadcasted to all subscribed and future observers, subject to buffer trimming policies.
/// </summary>
/// <typeparam name="T">The type of the elements processed by the subject.</typeparam>
public sealed class ReplaySubject<T> : ISubject<T>, IDisposable
{
private const int InfiniteBufferSize = int.MaxValue;
private readonly int _bufferSize;
private readonly TimeSpan _window;
private readonly IScheduler _scheduler;
private readonly IStopwatch _stopwatch;
private readonly Queue<TimeInterval<T>> _queue;
private bool _isStopped;
private Exception _error;
private ImmutableList<ScheduledObserver<T>> _observers;
private bool _isDisposed;
private readonly object _gate = new object();
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class with the specified buffer size, window and scheduler.
/// </summary>
/// <param name="bufferSize">Maximum element count of the replay buffer.</param>
/// <param name="window">Maximum time length of the replay buffer.</param>
/// <param name="scheduler">Scheduler the observers are invoked on.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bufferSize"/> is less than zero. -or- <paramref name="window"/> is less than TimeSpan.Zero.</exception>
/// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception>
public ReplaySubject(int bufferSize, TimeSpan window, IScheduler scheduler)
{
if (bufferSize < 0)
throw new ArgumentOutOfRangeException("bufferSize");
if (window < TimeSpan.Zero)
throw new ArgumentOutOfRangeException("window");
if (scheduler == null)
throw new ArgumentNullException("scheduler");
_bufferSize = bufferSize;
_window = window;
_scheduler = scheduler;
_stopwatch = _scheduler.StartStopwatch();
_queue = new Queue<TimeInterval<T>>();
_isStopped = false;
_error = null;
_observers = new ImmutableList<ScheduledObserver<T>>();
}
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class with the specified buffer size and window.
/// </summary>
/// <param name="bufferSize">Maximum element count of the replay buffer.</param>
/// <param name="window">Maximum time length of the replay buffer.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bufferSize"/> is less than zero. -or- <paramref name="window"/> is less than TimeSpan.Zero.</exception>
public ReplaySubject(int bufferSize, TimeSpan window)
: this(bufferSize, window, SchedulerDefaults.Iteration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class.
/// </summary>
public ReplaySubject()
: this(InfiniteBufferSize, TimeSpan.MaxValue, SchedulerDefaults.Iteration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class with the specified scheduler.
/// </summary>
/// <param name="scheduler">Scheduler the observers are invoked on.</param>
/// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception>
public ReplaySubject(IScheduler scheduler)
: this(InfiniteBufferSize, TimeSpan.MaxValue, scheduler)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class with the specified buffer size and scheduler.
/// </summary>
/// <param name="bufferSize">Maximum element count of the replay buffer.</param>
/// <param name="scheduler">Scheduler the observers are invoked on.</param>
/// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bufferSize"/> is less than zero.</exception>
public ReplaySubject(int bufferSize, IScheduler scheduler)
: this(bufferSize, TimeSpan.MaxValue, scheduler)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class with the specified buffer size.
/// </summary>
/// <param name="bufferSize">Maximum element count of the replay buffer.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bufferSize"/> is less than zero.</exception>
public ReplaySubject(int bufferSize)
: this(bufferSize, TimeSpan.MaxValue, SchedulerDefaults.Iteration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class with the specified window and scheduler.
/// </summary>
/// <param name="window">Maximum time length of the replay buffer.</param>
/// <param name="scheduler">Scheduler the observers are invoked on.</param>
/// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="window"/> is less than TimeSpan.Zero.</exception>
public ReplaySubject(TimeSpan window, IScheduler scheduler)
: this(InfiniteBufferSize, window, scheduler)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;" /> class with the specified window.
/// </summary>
/// <param name="window">Maximum time length of the replay buffer.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="window"/> is less than TimeSpan.Zero.</exception>
public ReplaySubject(TimeSpan window)
: this(InfiniteBufferSize, window, SchedulerDefaults.Iteration)
{
}
/// <summary>
/// Indicates whether the subject has observers subscribed to it.
/// </summary>
public bool HasObservers
{
get
{
var observers = _observers;
return observers != null && observers.Data.Length > 0;
}
}
void Trim(TimeSpan now)
{
while (_queue.Count > _bufferSize)
_queue.Dequeue();
while (_queue.Count > 0 && now.Subtract(_queue.Peek().Interval).CompareTo(_window) > 0)
_queue.Dequeue();
}
/// <summary>
/// Notifies all subscribed and future observers about the arrival of the specified element in the sequence.
/// </summary>
/// <param name="value">The value to send to all observers.</param>
public void OnNext(T value)
{
var o = default(ScheduledObserver<T>[]);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
var now = _stopwatch.Elapsed;
_queue.Enqueue(new TimeInterval<T>(value, now));
Trim(now);
o = _observers.Data;
foreach (var observer in o)
observer.OnNext(value);
}
}
if (o != null)
foreach (var observer in o)
observer.EnsureActive();
}
/// <summary>
/// Notifies all subscribed and future observers about the specified exception.
/// </summary>
/// <param name="error">The exception to send to all observers.</param>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
var o = default(ScheduledObserver<T>[]);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
var now = _stopwatch.Elapsed;
_isStopped = true;
_error = error;
Trim(now);
o = _observers.Data;
foreach (var observer in o)
observer.OnError(error);
_observers = new ImmutableList<ScheduledObserver<T>>();
}
}
if (o != null)
foreach (var observer in o)
observer.EnsureActive();
}
/// <summary>
/// Notifies all subscribed and future observers about the end of the sequence.
/// </summary>
public void OnCompleted()
{
var o = default(ScheduledObserver<T>[]);
lock (_gate)
{
CheckDisposed();
if (!_isStopped)
{
var now = _stopwatch.Elapsed;
_isStopped = true;
Trim(now);
o = _observers.Data;
foreach (var observer in o)
observer.OnCompleted();
_observers = new ImmutableList<ScheduledObserver<T>>();
}
}
if (o != null)
foreach (var observer in o)
observer.EnsureActive();
}
/// <summary>
/// Subscribes an observer to the subject.
/// </summary>
/// <param name="observer">Observer to subscribe to the subject.</param>
/// <returns>Disposable object that can be used to unsubscribe the observer from the subject.</returns>
/// <exception cref="ArgumentNullException"><paramref name="observer"/> is null.</exception>
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
var so = new ScheduledObserver<T>(_scheduler, observer);
var n = 0;
var subscription = new RemovableDisposable(this, so);
lock (_gate)
{
CheckDisposed();
//
// Notice the v1.x behavior of always calling Trim is preserved here.
//
// This may be subject (pun intended) of debate: should this policy
// only be applied while the sequence is active? With the current
// behavior, a sequence will "die out" after it has terminated by
// continuing to drop OnNext notifications from the queue.
//
// In v1.x, this behavior was due to trimming based on the clock value
// returned by scheduler.Now, applied to all but the terminal message
// in the queue. Using the IStopwatch has the same effect. Either way,
// we guarantee the final notification will be observed, but there's
// no way to retain the buffer directly. One approach is to use the
// time-based TakeLast operator and apply an unbounded ReplaySubject
// to it.
//
// To conclude, we're keeping the behavior as-is for compatibility
// reasons with v1.x.
//
Trim(_stopwatch.Elapsed);
_observers = _observers.Add(so);
n = _queue.Count;
foreach (var item in _queue)
so.OnNext(item.Value);
if (_error != null)
{
n++;
so.OnError(_error);
}
else if (_isStopped)
{
n++;
so.OnCompleted();
}
}
so.EnsureActive(n);
return subscription;
}
void Unsubscribe(ScheduledObserver<T> observer)
{
lock (_gate)
{
if (!_isDisposed)
_observers = _observers.Remove(observer);
}
}
sealed class RemovableDisposable : IDisposable
{
private readonly ReplaySubject<T> _subject;
private readonly ScheduledObserver<T> _observer;
public RemovableDisposable(ReplaySubject<T> subject, ScheduledObserver<T> observer)
{
_subject = subject;
_observer = observer;
}
public void Dispose()
{
_observer.Dispose();
_subject.Unsubscribe(_observer);
}
}
void CheckDisposed()
{
if (_isDisposed)
throw new ObjectDisposedException(string.Empty);
}
/// <summary>
/// Releases all resources used by the current instance of the <see cref="System.Reactive.Subjects.ReplaySubject&lt;T&gt;"/> class and unsubscribe all observers.
/// </summary>
public void Dispose()
{
lock (_gate)
{
_isDisposed = true;
_observers = null;
}
}
}
}

View File

@@ -0,0 +1,108 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reactive.Concurrency;
using System.Reactive.Linq;
namespace System.Reactive.Subjects
{
/// <summary>
/// Provides a set of static methods for creating subjects.
/// </summary>
public static class Subject
{
/// <summary>
/// Creates a subject from the specified observer and observable.
/// </summary>
/// <typeparam name="TSource">The type of the elements received by the observer.</typeparam>
/// <typeparam name="TResult">The type of the elements produced by the observable sequence.</typeparam>
/// <param name="observer">The observer used to send messages to the subject.</param>
/// <param name="observable">The observable used to subscribe to messages sent from the subject.</param>
/// <returns>Subject implemented using the given observer and observable.</returns>
/// <exception cref="ArgumentNullException"><paramref name="observer"/> or <paramref name="observable"/> is null.</exception>
public static ISubject<TSource, TResult> Create<TSource, TResult>(IObserver<TSource> observer, IObservable<TResult> observable)
{
if (observer == null)
throw new ArgumentNullException("observer");
if (observable == null)
throw new ArgumentNullException("observable");
return new AnonymousSubject<TSource, TResult>(observer, observable);
}
/// <summary>
/// Synchronizes the messages sent to the subject.
/// </summary>
/// <typeparam name="TSource">The type of the elements received by the subject.</typeparam>
/// <typeparam name="TResult">The type of the elements produced by the subject.</typeparam>
/// <param name="subject">The subject to synchronize.</param>
/// <returns>Subject whose messages are synchronized.</returns>
/// <exception cref="ArgumentNullException"><paramref name="subject"/> is null.</exception>
public static ISubject<TSource, TResult> Synchronize<TSource, TResult>(ISubject<TSource, TResult> subject)
{
if (subject == null)
throw new ArgumentNullException("subject");
return new AnonymousSubject<TSource, TResult>(Observer.Synchronize(subject), subject);
}
/// <summary>
/// Synchronizes the messages sent to the subject and notifies observers on the specified scheduler.
/// </summary>
/// <typeparam name="TSource">The type of the elements received by the subject.</typeparam>
/// <typeparam name="TResult">The type of the elements produced by the subject.</typeparam>
/// <param name="subject">The subject to synchronize.</param>
/// <param name="scheduler">Scheduler to notify observers on.</param>
/// <returns>Subject whose messages are synchronized and whose observers are notified on the given scheduler.</returns>
/// <exception cref="ArgumentNullException"><paramref name="subject"/> or <paramref name="scheduler"/> is null.</exception>
public static ISubject<TSource, TResult> Synchronize<TSource, TResult>(ISubject<TSource, TResult> subject, IScheduler scheduler)
{
if (subject == null)
throw new ArgumentNullException("subject");
if (scheduler == null)
throw new ArgumentNullException("scheduler");
return new AnonymousSubject<TSource, TResult>(Observer.Synchronize(subject), subject.ObserveOn(scheduler));
}
class AnonymousSubject<T, U> : ISubject<T, U>
{
private readonly IObserver<T> _observer;
private readonly IObservable<U> _observable;
public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
{
_observer = observer;
_observable = observable;
}
public void OnCompleted()
{
_observer.OnCompleted();
}
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
_observer.OnError(error);
}
public void OnNext(T value)
{
_observer.OnNext(value);
}
public IDisposable Subscribe(IObserver<U> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
//
// [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
//
return _observable.Subscribe/*Unsafe*/(observer);
}
}
}
}

View File

@@ -0,0 +1,396 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
#if !NO_PERF
using System.Reactive.Disposables;
using System.Threading;
namespace System.Reactive.Subjects
{
/// <summary>
/// Represents an object that is both an observable sequence as well as an observer.
/// Each notification is broadcasted to all subscribed observers.
/// </summary>
/// <typeparam name="T">The type of the elements processed by the subject.</typeparam>
public sealed class Subject<T> : ISubject<T>, IDisposable
{
private volatile IObserver<T> _observer;
/// <summary>
/// Creates a subject.
/// </summary>
public Subject()
{
_observer = NopObserver<T>.Instance;
}
/// <summary>
/// Indicates whether the subject has observers subscribed to it.
/// </summary>
public bool HasObservers
{
get
{
return _observer != NopObserver<T>.Instance && !(_observer is DoneObserver<T>) && _observer != DisposedObserver<T>.Instance;
}
}
/// <summary>
/// Notifies all subscribed observers about the end of the sequence.
/// </summary>
public void OnCompleted()
{
var oldObserver = default(IObserver<T>);
var newObserver = DoneObserver<T>.Completed;
do
{
oldObserver = _observer;
if (oldObserver == DisposedObserver<T>.Instance || oldObserver is DoneObserver<T>)
break;
#pragma warning disable 0420
} while (Interlocked.CompareExchange(ref _observer, newObserver, oldObserver) != oldObserver);
#pragma warning restore 0420
oldObserver.OnCompleted();
}
/// <summary>
/// Notifies all subscribed observers about the specified exception.
/// </summary>
/// <param name="error">The exception to send to all currently subscribed observers.</param>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
var oldObserver = default(IObserver<T>);
var newObserver = new DoneObserver<T> { Exception = error };
do
{
oldObserver = _observer;
if (oldObserver == DisposedObserver<T>.Instance || oldObserver is DoneObserver<T>)
break;
#pragma warning disable 0420
} while (Interlocked.CompareExchange(ref _observer, newObserver, oldObserver) != oldObserver);
#pragma warning restore 0420
oldObserver.OnError(error);
}
/// <summary>
/// Notifies all subscribed observers about the arrival of the specified element in the sequence.
/// </summary>
/// <param name="value">The value to send to all currently subscribed observers.</param>
public void OnNext(T value)
{
_observer.OnNext(value);
}
/// <summary>
/// Subscribes an observer to the subject.
/// </summary>
/// <param name="observer">Observer to subscribe to the subject.</param>
/// <returns>Disposable object that can be used to unsubscribe the observer from the subject.</returns>
/// <exception cref="ArgumentNullException"><paramref name="observer"/> is null.</exception>
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
var oldObserver = default(IObserver<T>);
var newObserver = default(IObserver<T>);
do
{
oldObserver = _observer;
if (oldObserver == DisposedObserver<T>.Instance)
{
throw new ObjectDisposedException("");
}
if (oldObserver == DoneObserver<T>.Completed)
{
observer.OnCompleted();
return Disposable.Empty;
}
var done = oldObserver as DoneObserver<T>;
if (done != null)
{
observer.OnError(done.Exception);
return Disposable.Empty;
}
if (oldObserver == NopObserver<T>.Instance)
{
newObserver = observer;
}
else
{
var obs = oldObserver as Observer<T>;
if (obs != null)
{
newObserver = obs.Add(observer);
}
else
{
newObserver = new Observer<T>(new ImmutableList<IObserver<T>>(new[] { oldObserver, observer }));
}
}
#pragma warning disable 0420
} while (Interlocked.CompareExchange(ref _observer, newObserver, oldObserver) != oldObserver);
#pragma warning restore 0420
return new Subscription(this, observer);
}
class Subscription : IDisposable
{
private Subject<T> _subject;
private IObserver<T> _observer;
public Subscription(Subject<T> subject, IObserver<T> observer)
{
_subject = subject;
_observer = observer;
}
public void Dispose()
{
var observer = Interlocked.Exchange(ref _observer, null);
if (observer == null)
return;
_subject.Unsubscribe(observer);
_subject = null;
}
}
private void Unsubscribe(IObserver<T> observer)
{
var oldObserver = default(IObserver<T>);
var newObserver = default(IObserver<T>);
do
{
oldObserver = _observer;
if (oldObserver == DisposedObserver<T>.Instance || oldObserver is DoneObserver<T>)
return;
var obs = oldObserver as Observer<T>;
if (obs != null)
{
newObserver = obs.Remove(observer);
}
else
{
if (oldObserver != observer)
return;
newObserver = NopObserver<T>.Instance;
}
#pragma warning disable 0420
} while (Interlocked.CompareExchange(ref _observer, newObserver, oldObserver) != oldObserver);
#pragma warning restore 0420
}
/// <summary>
/// Releases all resources used by the current instance of the <see cref="System.Reactive.Subjects.Subject&lt;T&gt;"/> class and unsubscribes all observers.
/// </summary>
public void Dispose()
{
_observer = DisposedObserver<T>.Instance;
}
}
}
#else
using System.Reactive.Disposables;
using System.Threading;
namespace System.Reactive.Subjects
{
/// <summary>
/// Represents an object that is both an observable sequence as well as an observer.
/// Each notification is broadcasted to all subscribed observers.
/// </summary>
/// <typeparam name="T">The type of the elements processed by the subject.</typeparam>
public sealed class Subject<T> : ISubject<T>, IDisposable
{
bool isDisposed;
bool isStopped;
ImmutableList<IObserver<T>> observers;
object gate = new object();
Exception exception;
/// <summary>
/// Creates a subject.
/// </summary>
public Subject()
{
observers = new ImmutableList<IObserver<T>>();
}
/// <summary>
/// Notifies all subscribed observers about the end of the sequence.
/// </summary>
public void OnCompleted()
{
var os = default(IObserver<T>[]);
lock (gate)
{
CheckDisposed();
if (!isStopped)
{
os = observers.Data;
observers = new ImmutableList<IObserver<T>>();
isStopped = true;
}
}
if (os != null)
foreach (var o in os)
o.OnCompleted();
}
/// <summary>
/// Notifies all subscribed observers with the exception.
/// </summary>
/// <param name="error">The exception to send to all subscribed observers.</param>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
var os = default(IObserver<T>[]);
lock (gate)
{
CheckDisposed();
if (!isStopped)
{
os = observers.Data;
observers = new ImmutableList<IObserver<T>>();
isStopped = true;
exception = error;
}
}
if (os != null)
foreach (var o in os)
o.OnError(error);
}
/// <summary>
/// Notifies all subscribed observers with the value.
/// </summary>
/// <param name="value">The value to send to all subscribed observers.</param>
public void OnNext(T value)
{
var os = default(IObserver<T>[]);
lock (gate)
{
CheckDisposed();
if (!isStopped)
{
os = observers.Data;
}
}
if (os != null)
foreach (var o in os)
o.OnNext(value);
}
/// <summary>
/// Subscribes an observer to the subject.
/// </summary>
/// <param name="observer">Observer to subscribe to the subject.</param>
/// <remarks>IDisposable object that can be used to unsubscribe the observer from the subject.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="observer"/> is null.</exception>
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
lock (gate)
{
CheckDisposed();
if (!isStopped)
{
observers = observers.Add(observer);
return new Subscription(this, observer);
}
else if (exception != null)
{
observer.OnError(exception);
return Disposable.Empty;
}
else
{
observer.OnCompleted();
return Disposable.Empty;
}
}
}
void Unsubscribe(IObserver<T> observer)
{
lock (gate)
{
if (observers != null)
observers = observers.Remove(observer);
}
}
class Subscription : IDisposable
{
Subject<T> subject;
IObserver<T> observer;
public Subscription(Subject<T> subject, IObserver<T> observer)
{
this.subject = subject;
this.observer = observer;
}
public void Dispose()
{
var o = Interlocked.Exchange<IObserver<T>>(ref observer, null);
if (o != null)
{
subject.Unsubscribe(o);
subject = null;
}
}
}
void CheckDisposed()
{
if (isDisposed)
throw new ObjectDisposedException(string.Empty);
}
/// <summary>
/// Unsubscribe all observers and release resources.
/// </summary>
public void Dispose()
{
lock (gate)
{
isDisposed = true;
observers = null;
}
}
}
}
#endif