/// <summary> /// Merges an observable sequence of observable sequences into an observable sequence. /// </summary> public static IObservable <TSource> Merge <TSource>(this IObservable <IObservable <TSource> > source) { if (source == null) { throw new ArgumentNullException("source"); } return(new AnonymousObservable <TSource>(observer => { var gate = new object(); var isStopped = false; var group = new CompositeDisposable(); var outerSubscription = new MutableDisposable(); group.Add(outerSubscription); outerSubscription.Disposable = source.Subscribe( innerSource => { var innerSubscription = new MutableDisposable(); group.Add(innerSubscription); innerSubscription.Disposable = innerSource.Subscribe( x => { lock (gate) observer.OnNext(x); }, exception => { lock (gate) observer.OnError(exception); }, () => { group.Remove(innerSubscription); // modification MUST occur before subsequent check if (isStopped && group.Count == 1) // isStopped must be checked before group Count to ensure outer is not creating more groups { lock (gate) observer.OnCompleted(); } }); }, exception => { lock (gate) observer.OnError(exception); }, () => { isStopped = true; // modification MUST occur before subsequent check if (group.Count == 1) { lock (gate) observer.OnCompleted(); } }); return group; })); }
public JoinObserver(IObservable <T> source, Action <Exception> onError) { this.source = source; this.onError = onError; Queue = new Queue <Notification <T> >(); subscription = new MutableDisposable(); activePlans = new List <ActivePlan>(); }
/// <summary> /// Asynchronously notify observers using the scheduler. /// </summary> public static IObservable <TSource> ObserveOn <TSource>(this IObservable <TSource> source, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } if (scheduler == null) { throw new ArgumentNullException("scheduler"); } return(new AnonymousObservable <TSource>(observer => { var q = new Queue <Notification <TSource> >(); var active = false; var gate = new object(); var cancelable = new MutableDisposable(); var subscription = source.Materialize().Subscribe(n => { var shouldStart = false; lock (gate) { shouldStart = !active; active = true; q.Enqueue(n); } if (shouldStart) { cancelable.Disposable = scheduler.Schedule(self => { var notification = default(Notification <TSource>); lock (gate) { notification = q.Dequeue(); } notification.Accept(observer); var shouldRecurse = false; lock (gate) { shouldRecurse = active = q.Count > 0; } if (shouldRecurse) { self(); } }); } }); return new CompositeDisposable(subscription, cancelable); })); }
/// <summary> /// Continues an observable sequence that is terminated normally or by an exception with the next observable sequence. /// </summary> public static IObservable <TSource> OnErrorResumeNext <TSource>(this IEnumerable <IObservable <TSource> > sources, IScheduler scheduler) { if (sources == null) { throw new ArgumentNullException("sources"); } if (scheduler == null) { throw new ArgumentNullException("scheduler"); } return(new AnonymousObservable <TSource>(observer => { var e = sources.GetEnumerator(); var subscription = new MutableDisposable(); var cancelable = scheduler.Schedule(self => { var hasNext = false; var current = default(IObservable <TSource>); try { hasNext = e.MoveNext(); if (hasNext) { current = e.Current; } else { e.Dispose(); } } catch (Exception exception) { observer.OnError(exception); e.Dispose(); return; } if (!hasNext) { observer.OnCompleted(); return; } var d = new MutableDisposable(); subscription.Disposable = d; d.Disposable = current.Subscribe(observer.OnNext, exception => self(), self); }); return new CompositeDisposable(subscription, cancelable); })); }
public void MutableDisposable_Dispose() { var disp = false; var m = new MutableDisposable(); var d = Disposable.Create(() => { disp = true; }); m.Disposable = d; Assert.AreSame(d, m.Disposable); Assert.IsFalse(disp); m.Dispose(); Assert.IsTrue(disp); Assert.IsNull(m.Disposable); }
internal static IObservable <TResult> Combine <TLeft, TRight, TResult>(this IObservable <TLeft> leftSource, IObservable <TRight> rightSource, Func <IObserver <TResult>, IDisposable, IDisposable, IObserver <Either <Notification <TLeft>, Notification <TRight> > > > combinerSelector) { return(new AnonymousObservable <TResult>(observer => { var leftSubscription = new MutableDisposable(); var rightSubscription = new MutableDisposable(); var combiner = combinerSelector(observer, leftSubscription, rightSubscription); var gate = new object(); leftSubscription.Disposable = leftSource.Materialize().Select(x => Either <Notification <TLeft>, Notification <TRight> > .CreateLeft(x)).Synchronize(gate).Subscribe(combiner); rightSubscription.Disposable = rightSource.Materialize().Select(x => Either <Notification <TLeft>, Notification <TRight> > .CreateRight(x)).Synchronize(gate).Subscribe(combiner); return new CompositeDisposable(leftSubscription, rightSubscription); })); }
public void FutureDisposable_DisposeAfterSet() { var disposed = false; var d = new MutableDisposable(); var dd = Disposable.Create(() => { disposed = true; }); d.Disposable = dd; Assert.AreSame(dd, d.Disposable); Assert.IsFalse(disposed); d.Dispose(); Assert.IsTrue(disposed); d.Dispose(); Assert.IsTrue(disposed); }
/// <summary> /// Continues an observable sequence that is terminated by an exception of the specified type with the observable sequence /// produced by the handler. /// </summary> public static IObservable <TSource> Catch <TSource, TException>(this IObservable <TSource> source, Func <TException, IObservable <TSource> > handler) where TException : Exception { if (source == null) { throw new ArgumentNullException("source"); } if (handler == null) { throw new ArgumentNullException("handler"); } return(new AnonymousObservable <TSource>(observer => { var subscription = new MutableDisposable(); subscription.Disposable = source.Subscribe(observer.OnNext, exception => { var e = exception as TException; if (e != null) { IObservable <TSource> result; try { result = handler(e); } catch (Exception ex) { observer.OnError(ex); return; } var d = new MutableDisposable(); subscription.Disposable = d; d.Disposable = result.Subscribe(observer); } else { observer.OnError(exception); } }, observer.OnCompleted); return subscription; })); }
/// <summary> /// Asynchronously subscribes and unsubscribes observers on the synchronization context. /// </summary> public static IObservable <TSource> SubscribeOn <TSource>(this IObservable <TSource> source, SynchronizationContext context) { if (source == null) { throw new ArgumentNullException("source"); } if (context == null) { throw new ArgumentNullException("context"); } return(new AnonymousObservable <TSource>(observer => { var subscription = new MutableDisposable(); context.Post(_ => subscription.Disposable = new ContextDisposable(context, source.Subscribe(observer)), null); return subscription; })); }
public void FutureDisposable_DisposeBeforeSet() { var disposed = false; var d = new MutableDisposable(); var dd = Disposable.Create(() => { disposed = true; }); Assert.IsFalse(disposed); d.Dispose(); Assert.IsFalse(disposed); d.Disposable = dd; Assert.IsNull(d.Disposable); // CHECK! Assert.IsTrue(disposed); d.Dispose(); Assert.IsTrue(disposed); }
public void MutableDisposable_ReplaceBeforeDispose() { var disp1 = false; var disp2 = false; var m = new MutableDisposable(); var d1 = Disposable.Create(() => { disp1 = true; }); m.Disposable = d1; Assert.AreSame(d1, m.Disposable); Assert.IsFalse(disp1); var d2 = Disposable.Create(() => { disp2 = true; }); m.Disposable = d2; Assert.AreSame(d2, m.Disposable); Assert.IsTrue(disp1); Assert.IsFalse(disp2); }
/// <summary> /// Asynchronously subscribes and unsubscribes observers using scheduler. /// </summary> public static IObservable <TSource> SubscribeOn <TSource>(this IObservable <TSource> source, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } if (scheduler == null) { throw new ArgumentNullException("scheduler"); } return(new AnonymousObservable <TSource>(observer => { var d = new MutableDisposable(); scheduler.Schedule(() => d.Disposable = new ScheduledDisposable(scheduler, source.Subscribe(observer))); return d; })); }
public void MutableDisposable_ReplaceAfterDispose() { var disp1 = false; var disp2 = false; var m = new MutableDisposable(); m.Dispose(); var d1 = Disposable.Create(() => { disp1 = true; }); m.Disposable = d1; Assert.IsNull(m.Disposable); // CHECK Assert.IsTrue(disp1); var d2 = Disposable.Create(() => { disp2 = true; }); m.Disposable = d2; Assert.IsNull(m.Disposable); // CHECK Assert.IsTrue(disp2); }
/// <summary> /// Ignores values from an observable sequence which are followed by another value before dueTime. /// </summary> public static IObservable <TSource> Throttle <TSource>(this IObservable <TSource> source, TimeSpan dueTime, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } if (scheduler == null) { throw new ArgumentNullException("scheduler"); } return(new AnonymousObservable <TSource>(observer => { var gate = new object(); var value = default(TSource); var hasValue = false; var cancelable = new MutableDisposable(); var id = 0UL; var subscription = source.Subscribe(x => { ulong currentid; lock (gate) { hasValue = true; value = x; id = unchecked (id + 1); currentid = id; } var d = new MutableDisposable(); cancelable.Disposable = d; d.Disposable = scheduler.Schedule(() => { lock (gate) { if (hasValue && id == currentid) { observer.OnNext(value); } hasValue = false; } }, dueTime); }, exception => { cancelable.Dispose(); lock (gate) { observer.OnError(exception); hasValue = false; id = unchecked (id + 1); } }, () => { cancelable.Dispose(); lock (gate) { if (hasValue) { observer.OnNext(value); } observer.OnCompleted(); hasValue = false; id = unchecked (id + 1); } }); return new CompositeDisposable(subscription, cancelable); })); }
/// <summary> /// Returns the source observable sequence or the other observable sequence if dueTime elapses. /// </summary> public static IObservable <TSource> Timeout <TSource>(this IObservable <TSource> source, TimeSpan dueTime, IObservable <TSource> other, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } if (scheduler == null) { throw new ArgumentNullException("scheduler"); } if (other == null) { throw new ArgumentNullException("other"); } return(new AnonymousObservable <TSource>(observer => { var subscription = new MutableDisposable(); var timer = new MutableDisposable(); var original = new MutableDisposable(); subscription.Disposable = original; var gate = new object(); var id = 0UL; var initial = id; var switched = false; timer.Disposable = scheduler.Schedule(() => { var timerWins = false; lock (gate) { switched = id == initial; timerWins = switched; } if (timerWins) { subscription.Disposable = other.Subscribe(observer); } }, dueTime); original.Disposable = source.Subscribe( x => { var onNextWins = false; var value = 0UL; lock (gate) { onNextWins = !switched; if (onNextWins) { id = unchecked (id + 1); value = id; } } if (onNextWins) { observer.OnNext(x); timer.Disposable = scheduler.Schedule(() => { var timerWins = false; lock (gate) { switched = id == value; timerWins = switched; } if (timerWins) { subscription.Disposable = other.Subscribe(observer); } }, dueTime); } }, exception => { var onErrorWins = false; lock (gate) { onErrorWins = !switched; if (onErrorWins) { id = unchecked (id + 1); } } if (onErrorWins) { observer.OnError(exception); } }, () => { var onCompletedWins = false; lock (gate) { onCompletedWins = !switched; if (onCompletedWins) { id = unchecked (id + 1); } } if (onCompletedWins) { observer.OnCompleted(); } }); return new CompositeDisposable(subscription, timer); })); }
public void FutureDisposable_SetNull() { var d = new MutableDisposable(); d.Disposable = null; }
/// <summary> /// Returns the source observable sequence or the other observable sequence if dueTime elapses. /// </summary> public static IObservable <TSource> Timeout <TSource>(this IObservable <TSource> source, DateTimeOffset dueTime, IObservable <TSource> other, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } if (scheduler == null) { throw new ArgumentNullException("scheduler"); } if (other == null) { throw new ArgumentNullException("other"); } return(new AnonymousObservable <TSource>(observer => { var subscription = new MutableDisposable(); var original = new MutableDisposable(); subscription.Disposable = original; var gate = new object(); var switched = false; var timer = scheduler.Schedule(() => { var timerWins = false; lock (gate) { timerWins = !switched; switched = true; } if (timerWins) { subscription.Disposable = other.Subscribe(observer); } }, dueTime); original.Disposable = source.Subscribe( x => { lock (gate) { if (!switched) { observer.OnNext(x); } } }, exception => { var onErrorWins = false; lock (gate) { onErrorWins = !switched; switched = true; } if (onErrorWins) { observer.OnError(exception); } }, () => { var onCompletedWins = false; lock (gate) { onCompletedWins = !switched; switched = true; } if (onCompletedWins) { observer.OnCompleted(); } }); return new CompositeDisposable(subscription, timer); })); }
public void MutableDisposable_Ctor_Prop() { var m = new MutableDisposable(); Assert.IsNull(m.Disposable); }
/// <summary> /// Groups the elements of an observable sequence according to a specified key selector function and comparer and selects the resulting elements by using a specified function. /// </summary> public static IObservable <IGroupedObservable <TKey, TElement> > GroupBy <TSource, TKey, TElement>(this IObservable <TSource> source, Func <TSource, TKey> keySelector, Func <TSource, TElement> elementSelector, IEqualityComparer <TKey> comparer) { if (source == null) { throw new ArgumentNullException("source"); } if (keySelector == null) { throw new ArgumentNullException("keySelector"); } if (elementSelector == null) { throw new ArgumentNullException("elementSelector"); } if (comparer == null) { throw new ArgumentNullException("comparer"); } return(new AnonymousObservable <IGroupedObservable <TKey, TElement> >(observer => { var map = new Dictionary <TKey, Subject <TElement> >(comparer); var subscription = new MutableDisposable(); var refCountDisposable = new RefCountDisposable(subscription); subscription.Disposable = source.Subscribe(x => { var key = default(TKey); try { key = keySelector(x); } catch (Exception exception) { foreach (var w in map.Values) { w.OnError(exception); } observer.OnError(exception); return; } var fireNewMapEntry = false; var writer = default(Subject <TElement>); try { if (!map.TryGetValue(key, out writer)) { writer = new Subject <TElement>(); map.Add(key, writer); fireNewMapEntry = true; } } catch (Exception exception) { foreach (var w in map.Values) { w.OnError(exception); } observer.OnError(exception); return; } if (fireNewMapEntry) { observer.OnNext(new GroupedObservable <TKey, TElement>(key, writer, refCountDisposable)); } var element = default(TElement); try { element = elementSelector(x); } catch (Exception exception) { foreach (var w in map.Values) { w.OnError(exception); } observer.OnError(exception); return; } writer.OnNext(element); }, e => { foreach (var w in map.Values) { w.OnError(e); } observer.OnError(e); }, () => { foreach (var w in map.Values) { w.OnCompleted(); } observer.OnCompleted(); }); return refCountDisposable; })); }
public static IObservable <TSource[]> BufferWithTimeout <TSource>( this IObservable <TSource> source, TimeSpan timeout, IScheduler scheduler, bool flushFirst = false) { return(Observable.CreateWithDisposable <TSource[]>( observer => { object lockObject = new object(); List <TSource> buffer = new List <TSource>(); bool fistToBuffer = true; MutableDisposable timeoutDisposable = new MutableDisposable(); Action flushBuffer = () => { TSource[] values; lock (lockObject) { values = buffer.ToArray(); buffer.Clear(); fistToBuffer = true; } observer.OnNext(values); }; var sourceSubscription = source.Subscribe( value => { if (fistToBuffer && flushFirst) { bool isFirst = false; lock (lockObject) { if (fistToBuffer) { fistToBuffer = false; isFirst = true; } } if (isFirst) { observer.OnNext(new[] { value }); return; } } lock (lockObject) { buffer.Add(value); } timeoutDisposable.Disposable = scheduler.Schedule(flushBuffer, timeout); }, observer.OnError, () => { flushBuffer(); observer.OnCompleted(); }); return new CompositeDisposable(sourceSubscription, timeoutDisposable); })); }
/// <summary> /// Projects each value of an observable sequence into a buffer that's sent out when either it's full or a given amount of time has elapsed. /// </summary> public static IObservable <IList <TSource> > BufferWithTimeOrCount <TSource>(this IObservable <TSource> source, TimeSpan timeSpan, int count, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } if (count <= 0) { throw new ArgumentOutOfRangeException("count"); } if (scheduler == null) { throw new ArgumentNullException("scheduler"); } return(new AnonymousObservable <IList <TSource> >(observer => { var bufferId = 0UL; var gate = new object(); var data = new List <TSource>(); var flushBuffer = new Action(() => { observer.OnNext(data); data = new List <TSource>(); bufferId = unchecked (bufferId + 1); }); var timer = new MutableDisposable(); var startTimer = default(Action <ulong>); startTimer = myId => { var workItem = scheduler.Schedule(() => { var shouldRecurse = false; var nextId = 0UL; lock (gate) { if (myId == bufferId) { flushBuffer(); nextId = bufferId; shouldRecurse = true; } } if (shouldRecurse) { startTimer(nextId); } }, timeSpan); timer.Disposable = workItem; }; startTimer(bufferId); var subscription = source.Subscribe( x => { var shouldStartNewTimer = false; var nextId = 0UL; lock (gate) { data.Add(x); if (data.Count == count) { flushBuffer(); nextId = bufferId; shouldStartNewTimer = true; } } if (shouldStartNewTimer) { startTimer(nextId); } }, exception => { lock (gate) { observer.OnNext(data); bufferId = unchecked (bufferId + 1); observer.OnError(exception); } }, () => { lock (gate) { observer.OnNext(data); bufferId = unchecked (bufferId + 1); observer.OnCompleted(); } } ); return new CompositeDisposable(subscription, timer); })); }
/// <summary> /// Transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. /// </summary> public static IObservable <TSource> Switch <TSource>(this IObservable <IObservable <TSource> > source) { if (source == null) { throw new ArgumentNullException("source"); } return(new AnonymousObservable <TSource>(observer => { var gate = new object(); var innerSubscription = new MutableDisposable(); var subscription = new MutableDisposable(); var isStopped = false; subscription.Disposable = source.Subscribe( innerSource => { var d = new MutableDisposable(); innerSubscription.Disposable = d; d.Disposable = innerSource.Subscribe( x => { lock (gate) observer.OnNext(x); }, exception => { subscription.Dispose(); innerSubscription.Dispose(); lock (gate) observer.OnError(exception); }, () => { innerSubscription.Disposable = null; if (isStopped) { lock (gate) observer.OnCompleted(); } }); }, exception => { innerSubscription.Dispose(); lock (gate) observer.OnError(exception); }, () => { isStopped = true; if (innerSubscription.Disposable == null) { lock (gate) observer.OnCompleted(); } }); return new CompositeDisposable(subscription, innerSubscription); })); }