/// <summary> /// Creates a collection based on an an Observable by adding items /// provided until the Observable completes, optionally ensuring a /// delay. Note that if the Observable never completes and withDelay is /// set, this method will leak a Timer. This method also guarantees that /// items are always added via the UI thread. /// </summary> /// <param name="fromObservable">The Observable whose items will be put /// into the new collection.</param> /// <param name="withDelay">If set, items will be populated in the /// collection no faster than the delay provided.</param> /// <returns>A new collection which will be populated with the /// Observable.</returns> public static ReactiveCollection <T> CreateCollection <T>( this IObservable <T> fromObservable, TimeSpan?withDelay = null) { var ret = new ReactiveCollection <T>(); if (withDelay == null) { fromObservable.ObserveOn(RxApp.DeferredScheduler).Subscribe(ret.Add); return(ret); } // On a timer, dequeue items from queue if they are available var queue = new Queue <T>(); var disconnect = Observable.Timer(withDelay.Value, RxApp.DeferredScheduler) .Subscribe(_ => { if (queue.Count > 0) { ret.Add(queue.Dequeue()); } }); // When new items come in from the observable, stuff them in the queue. // Using the DeferredScheduler guarantees we'll always access the queue // from the same thread. fromObservable.ObserveOn(RxApp.DeferredScheduler).Subscribe(queue.Enqueue); // This is a bit clever - keep a running count of the items actually // added and compare them to the final count of items provided by the // Observable. Combine the two values, and when they're equal, // disconnect the timer ret.ItemsAdded.Scan(0, ((acc, _) => acc + 1)).Zip(fromObservable.Aggregate(0, (acc, _) => acc + 1), (l, r) => (l == r)).Where(x => x).Subscribe(_ => disconnect.Dispose()); return(ret); }