The code snippet below is my attempt at creating the following functionality:
- Create an observable sequence that subscribes to a collection of subjects
- When one of the subjects in the collection produces a value, the sequence ends, invokes a method that returns a new set of subjects and restarts at 1.
- When the subscription to the outer observable gets disposed the whole thing stops
Questions about my implementation:
- Why does it work using subjectsSub.SelectMany(x => x).Merge() but not using subjectsSub.Merge()? (I would have expected the latter approach to work)
- Is there a simpler, more elegant solution to this in the vast arsenal of Rx Functionality?
Update: This sample was actually back-ported from RxJS-Typescript to reach a broader audience with this question. The fact that the original runs in a single-threaded browser environment using Javascript should make it clearer why this "observable capturing" may work (it does and without dirty hacks like messing with RxJs internals).
class Program
{
private static readonly Queue<IObservable<Unit>[]> observableDependencies = new Queue<IObservable<Unit>[]>();
private static IObservable<Unit>[] EvaluateExpressionAndCaptureTouchedObservables(Func<object> expression)
{
// wire some traps for capturing any observables "touched" by expression
expression();
// return observables touched by expression (not in this example of course)
if (observableDependencies.Count > 0)
return observableDependencies.Dequeue();
return new[] {Observable.Never<Unit>()}; // keep going
}
private static IObservable<Unit> CreateObservable(
Subject<IObservable<Unit>[]> capturedObservables, Stopwatch sw)
{
return Observable.Create<Unit>(observer =>
{
var isComplete = new Subject<Unit>();
var isAborted = false;
var disp = Scheduler.Default.Schedule(self =>
{
Console.WriteLine("** Next iteration at {0}", sw.Elapsed);
capturedObservables.SelectMany(x => x).Merge().TakeUntil(isComplete).Subscribe(x =>
{
observer.OnNext(Unit.Default);
// self-destruct
isComplete.OnNext(Unit.Default);
},
() =>
{
Console.WriteLine("completed");
if (!isAborted)
self();
});
capturedObservables.OnNext(EvaluateExpressionAndCaptureTouchedObservables());
});
return new CompositeDisposable(Disposable.Create(() =>
{
isAborted = true;
// self-destruct
isComplete.OnNext(Unit.Default);
}), disp);
});
}
private static void Main(string[] args)
{
var sw = new Stopwatch();
sw.Start();
observableDependencies.Enqueue(new[]
{
Observable.Timer(TimeSpan.FromSeconds(10)).Select(x => Unit.Default)
});
observableDependencies.Enqueue(new[]
{
Observable.Timer(TimeSpan.FromSeconds(5)).Select(x => Unit.Default),
Observable.Return(10).Select(x => Unit.Default)
});
observableDependencies.Enqueue(new[] {Observable.Timer(TimeSpan.FromSeconds(3)).Select(x => Unit.Default)});
var capturedObservables = new Subject<IObservable<Unit>[]>();
var obs = CreateObservable(capturedObservables, sw);
var disp = obs.Subscribe(x => Console.WriteLine("** fired at {0}", sw.Elapsed));
Console.ReadLine();
disp.Dispose();
Console.ReadLine();
}
}