I have the below repro code which demonstrate a problem in a more complex flow:
static void Main(string[] args)
{
var r = Observable.Range(1, 10).Finally(() => Console.WriteLine("Disposed"));
var x = Observable.Create<int>(o =>
{
for (int i = 1; i < 11; i++)
{
o.OnNext(i);
}
o.OnCompleted();
return Disposable.Create(() => Console.WriteLine("Disposed"));
});
var src = x.Publish().RefCount();
var a = src.Where(i => i % 2 == 0).Do(i => Console.WriteLine("Pair:" + i));
var b = src.Where(i => i % 2 != 0).Do(i => Console.WriteLine("Even:" + i));
var c = Observable.Merge(a, b);
using (c.Subscribe(i => Console.WriteLine("final " + i), () => Console.WriteLine("Complete")))
{
Console.ReadKey();
}
}
running this snippet with r as src (var src = r.Publish().RefCount()) will produce all the numbers from 1 till 10,
switching the src to x(like in example) will produce only the pairs, actually the first observable to subscribe unless i change Publish() to Replay().
Why? What is the difference between r and x?
Thanks.
Although I do not have the patience to sort through the Rx.NET source code to find exactly what implementation detail causes this exact behavior, I can provide the following insight:
The difference in behavior your are seeing is caused by a race condition. The racers in this case are the subscriptions of a and b which happen as a result of your subscription to the observable returned by Observable.Merge. You subscribe to c, which in turn subscribes to a and b. a and b are defined in terms of a Publish and RefCount of either x or r, depending on which case you choose.
Here's what's happening.
src = r
In this case, you are using a custom Observable. When subscribed to, your custom observible immediately and synchronously begins to onNext the numbers 1 though 10, and then calls onCompleted. Interestingly enough, this subscription is caused by your Publish().RefCount() Observable when it is subscribe to the first time. It is subscribed to the first time by a, because a is the first parameter to Merge. So, before Merge has even subscribed to b, your subscription has already completed. Merge subscribes to b, which is the RefCount observable. That observable is already completed, so Merge looks for the next Observable to merge. Since there are no more Observables to merge, and because all of the existing Observables have completed, the merged observable completes.
The values onNext'd through your custom observable have traveled through the "pairs" observable, but not the "evens" observable. Therefore, you end up with the following:
// "pairs" (has this been named incorrectly?)
[2, 4, 6, 8, 10]
src = x
In this case, you are using the built-in Range method to create an Observable. When subscribed to, this Range Observable does something that eventually ends up yielding the numbers 1 though 10. Interesting. We haven't a clue what's happening in that method, or when it's happening. We can, however, make some observations about it. If we look at what happens when src = r (above), we can see that only the first subscription takes effect because the observable is yielding immediately and synchronously. Therefore, we can determine that the Range Observable must not be yielding in the same manner, but instead allows the application's control flow to execute the subscription to b before any values are yielded. The difference between your custom Observable and this Range Observable, is probably that the Range Observable is scheduling the yields to happen on the CurrentThread Scheduler.
How to avoid this kind of race condition:
var src = a.Publish(); // not ref count
var a = src.where(...);
var b = src.where(...);
var c = Observable.Merge(a, b);
var subscription = c.Subscribe(i => Console.WriteLine("final " + i), () => Console.WriteLine("Complete"))
// don't dispose of the subscription. The observable creates an auto-disposing subscription which will call dispose once `OnCompleted` or `OnError` is called.
src.Connect(); // connect to the underlying observable, *after* merge has subscribed to both a and b.
Notice that the solution to fixing the subscription to this composition of Observables was not to change how the source observable works, but instead to make sure your subscription logic isn't allowing any race conditions to exist. This is important, because trying to fix this problem in the Observable is simply changing behavior, not fixing the race. Had we changed the source and switched it out later, the subscription logic would still be buggy.
I suspect it's the schedulers. This change causes the two to behave identically:
var x = Observable.Create<int>(o =>
{
NewThreadScheduler.Default.Schedule(() =>
{
for (int i = 1; i < 11; i++)
{
o.OnNext(i);
}
o.OnCompleted();
});
return Disposable.Create(() => Console.WriteLine("Disposed"));
});
Whereas using Scheduler.Immediate gives the same behavior as yours.
Related
I've got a simple program here that displays the number of letters in various words. It works as expected.
static void Main(string[] args) {
var word = new Subject<string>();
var wordPub = word.Publish().RefCount();
var length = word.Select(i => i.Length);
var report =
wordPub
.GroupJoin(length,
s => wordPub,
s => Observable.Empty<int>(),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.Select(j => new { Word = i.Word, Length = j }));
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Donkey");
word.OnNext("Elephant");
word.OnNext("Zebra");
Console.ReadLine();
}
And the output is:
Apple 5
Banana 6
Cat 3
Donkey 6
Elephant 8
Zebra 5
I used the Publish().RefCount() because "wordpub" is included in "report" twice. Without it, when a word is emitted first one part of the report would get notified by a callback, and then the other part of report would be notified, double the notifications. That is kindof what happens; the output ends up having 11 items rather than 6. At least that is what I think is going on. I think of using Publish().RefCount() in this situation as simultaneously updating both parts of the report.
However if I change the length function to ALSO use the published source like this:
var length = wordPub.Select(i => i.Length);
Then the output is this:
Apple 5
Apple 6
Banana 6
Cat 3
Banana 3
Cat 6
Donkey 6
Elephant 8
Donkey 8
Elephant 5
Zebra 5
Why can't the length function also use the same published source?
This was a great challenge to solve!
So subtle the conditions that this happens.
Apologies in advance for the long explanation, but bear with me!
TL;DR
Subscriptions to the published source are processed in order, but before any other subscription directly to the unpublished source. i.e. you can jump the queue!
With GroupJoin subscription order is important to determine when windows open and close.
My first concern would be that you are publish refcounting a subject.
This should be a no-op.
Subject<T> has no subscription cost.
So when you remove the Publish().RefCount() :
var word = new Subject<string>();
var wordPub = word;//.Publish().RefCount();
var length = word.Select(i => i.Length);
then you get the same issue.
So then I look to the GroupJoin (because my intuition suggests that Publish().Refcount() is a red herring).
For me, eyeballing this alone was too hard to rationalise, so I lean on a simple debugging too I have used dozens of times of the years - a Trace or Log extension method.
public interface ILogger
{
void Log(string input);
}
public class DumpLogger : ILogger
{
public void Log(string input)
{
//LinqPad `Dump()` extension method.
// Could use Console.Write instead.
input.Dump();
}
}
public static class ObservableLoggingExtensions
{
private static int _index = 0;
public static IObservable<T> Log<T>(this IObservable<T> source, ILogger logger, string name)
{
return Observable.Create<T>(o =>
{
var index = Interlocked.Increment(ref _index);
var label = $"{index:0000}{name}";
logger.Log($"{label}.Subscribe()");
var disposed = Disposable.Create(() => logger.Log($"{label}.Dispose()"));
var subscription = source
.Do(
x => logger.Log($"{label}.OnNext({x.ToString()})"),
ex => logger.Log($"{label}.OnError({ex})"),
() => logger.Log($"{label}.OnCompleted()")
)
.Subscribe(o);
return new CompositeDisposable(subscription, disposed);
});
}
}
When I add the logging to your provided code it looks like this:
var logger = new DumpLogger();
var word = new Subject<string>();
var wordPub = word.Publish().RefCount();
var length = word.Select(i => i.Length);
var report =
wordPub.Log(logger, "lhs")
.GroupJoin(word.Select(i => i.Length).Log(logger, "rhs"),
s => wordPub.Log(logger, "lhsDuration"),
s => Observable.Empty<int>().Log(logger, "rhsDuration"),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.Select(j => new { Word = i.Word, Length = j }));
report.Subscribe(i => ($"{i.Word} {i.Length}").Dump("OnNext"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Donkey");
word.OnNext("Elephant");
word.OnNext("Zebra");
This will then output in my log something like the following
Log with Publish().RefCount() used
0001lhs.Subscribe()
0002rhs.Subscribe()
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe()
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe()
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Banana 6
...
However when I remove the usage Publish().RefCount() the new log output is as follows:
Log without only Subject
0001lhs.Subscribe()
0002rhs.Subscribe()
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe()
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe()
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Apple 6
OnNext
Banana 6
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
...
This gives us some insight, however when the issue really becomes clear is when we start annotating our logs with a logical list of subscriptions.
In the original (working) code with the RefCount our annotations might look like this
//word.Subsribers.Add(wordPub)
0001lhs.Subscribe() //wordPub.Subsribers.Add(0001lhs)
0002rhs.Subscribe() //word.Subsribers.Add(0002rhs)
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe() //wordPub.Subsribers.Add(0003lhsDuration)
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe() //wordPub.Subsribers.Add(0005lhsDuration)
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose() //wordPub.Subsribers.Remove(0003lhsDuration)
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Banana 6
So in this example, when word.OnNext("Banana"); is executed the chain of observers is linked in this order
wordPub
0002rhs
However, wordPub has child subscriptions!
So the real subscription list looks like
wordPub
0001lhs
0003lhsDuration
0005lhsDuration
0002rhs
If we annotate the Subject only log we see where the subtlety lies
0001lhs.Subscribe() //word.Subsribers.Add(0001lhs)
0002rhs.Subscribe() //word.Subsribers.Add(0002rhs)
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe() //word.Subsribers.Add(0003lhsDuration)
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe() //word.Subsribers.Add(0005lhsDuration)
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Apple 6
OnNext
Banana 6
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
So in this example, when word.OnNext("Banana"); is executed the chain of observers is linked in this order
1. 0001lhs
2. 0002rhs
3. 0003lhsDuration
4. 0005lhsDuration
As the 0003lhsDuration subscription is activated after the 0002rhs, it wont see the "Banana" value to terminate the window, until after the rhs has been sent the value, thus yielding it in the still open window.
Whew
As #francezu13k50 points out the obvious and simple solution to your problem is to just use word.Select(x => new { Word = x, Length = x.Length });, but as I think you have given us a simplified version of your real problem (appreciated) I understand why this isn't suitable.
However, as I dont know what your real problem space is I am not sure what to suggest to you to provide a solution, except that you have one with your current code, and now you should know why it works the way it does.
RefCount returns an Observable that stays connected to the source as long as there is at least one subscription to the returned Observable. When the last subscription is disposed, RefCount disposes it's connection to the source, and reconnects when a new subscription is being made. It might be the case with your report query that all subscriptions to the 'wordPub' are disposed before the query is fulfilled.
Instead of the complicated GroupJoin query you could simply do :
var report = word.Select(x => new { Word = x, Length = x.Length });
Edit:
Change your report query to this if you want to use the GroupJoin operator :
var report =
wordPub
.GroupJoin(length,
s => wordPub,
s => Observable.Empty<int>(),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.FirstAsync().Select(j => new { Word = i.Word, Length = j }));
Because GroupJoin seems to be very tricky to work with, here is another approach for correlating the inputs and outputs of functions.
static void Main(string[] args) {
var word = new Subject<string>();
var length = new Subject<int>();
var report =
word
.CombineLatest(length, (w, l) => new { Word = w, Length = l })
.Scan((a, b) => new { Word = b.Word, Length = a.Word == b.Word ? b.Length : -1 })
.Where(i => i.Length != -1);
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple"); length.OnNext(5);
word.OnNext("Banana");
word.OnNext("Cat"); length.OnNext(3);
word.OnNext("Donkey");
word.OnNext("Elephant"); length.OnNext(8);
word.OnNext("Zebra"); length.OnNext(5);
Console.ReadLine();
}
This approach works if every input has 0 or more outputs subject to the constraints that (1) outputs only arrive in the same order as the inputs AND (2) each output corresponds to its most recent input. This is like a LeftJoin - each item in the first list (word) is paired with items in the right list (length) that subsequently arrive, up until another item in the first list is emitted.
Trying to use regular Join instead of GroupJoin. I thought the problem was that when a new word was created there was a race condition inside Join between creating a new window and ending the current one. So here I tried to elimate that by pairing every word with a null signifying the end of the window. Doesn't work, just like the first version did not. How is it possible that a new window is created for each word without the previous one being closed first? Completely confused.
static void Main(string[] args) {
var lgr = new DelegateLogger(Console.WriteLine);
var word = new Subject<string>();
var wordDelimited =
word
.Select(i => Observable.Return<string>(null).StartWith(i))
.SelectMany(i => i);
var wordStart = wordDelimited.Where(i => i != null);
var wordEnd = wordDelimited.Where(i => i == null);
var report = Observable
.Join(
wordStart.Log(lgr, "word"), // starts window
wordStart.Select(i => i.Length),
s => wordEnd.Log(lgr, "expireWord"), // ends current window
s => Observable.Empty<int>(),
(l, r) => new { Word = l, Length = r });
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Zebra");
word.OnNext("Elephant");
word.OnNext("Bear");
Console.ReadLine();
}
I have a problem that I do not know how to handle beautifully with RX.
I have multiple streams that all supposedly contain the same elements
However each stream may lose messages (UDP is involved) or be late/early compared to others. Each of these messages have a sequence number.
Now what I want to achieve is get a single stream out of all those streams, without duplicate and keeping the message order. In other words, the same sequence number should not appear twice and their values only have to increase, never decrease.
When a message was lost on all the streams, I'm OK with losing it (as there is another TCP mechanism involved that allows me to ask explicitly for missing messages).
I am looking to do that in RxJava, but I guess my problem is not specific to Java.
Here's a marble diagram to help visualizing what I want to achieve:
marble diagram
You can see in that diagram that we are waiting for 2 on the first stream to output 3 from the second stream.
Likewise, 6 is only outputted once we receive 6 from the second stream because only at that point can we know for sure that 5 will never be received by any stream.
This is browser code, but I think it should give you a good idea of how you could solve this.
public static IObservable<T> Sequenced<T>(
this IObservable<T> source,
Func<T, int> getSequenceNumber,
int sequenceBegin,
int sequenceRedundancy)
{
return Observable.Create(observer =>
{
// The next sequence number in order.
var sequenceNext = sequenceBegin;
// The key is the sequence number.
// The value is (T, Count).
var counts = new SortedDictionary<int, Tuple<T, int>>();
return source.Subscribe(
value =>
{
var sequenceNumber = getSequenceNumber(value);
// If the sequence number for the current value is
// earlier in the sequence, just throw away this value.
if (sequenceNumber < sequenceNext)
{
return;
}
// Update counts based on the current value.
Tuple<T, int> count;
if (!counts.TryGetValue(sequenceNumber, out count))
{
count = Tuple.Create(value, 0);
}
count = Tuple.Create(count.Item1, count.Item2 + 1);
counts[sequenceNumber] = count;
// If the current count has reached sequenceRedundancy,
// that means any seqeunce values S such that
// sequenceNext < S < sequenceNumber and S has not been
// seen yet will never be seen. So we emit everything
// we have seen up to this point, in order.
if (count.Item2 >= sequenceRedundancy)
{
var removal = counts.Keys
.TakeWhile(seq => seq <= sequenceNumber)
.ToList();
foreach (var seq in removal)
{
count = counts[seq];
observer.OnNext(count.Item1);
counts.Remove(seq);
}
sequenceNext++;
}
// Emit stored values as long as we keep having the
// next sequence value.
while (counts.TryGetValue(sequenceNext, out count))
{
observer.OnNext(count.Item1);
counts.Remove(sequenceNext);
sequenceNext++;
}
},
observer.OnError,
() =>
{
// Emit in order any remaining values.
foreach (var count in counts.Values)
{
observer.OnNext(count.Item1);
}
observer.OnCompleted();
});
});
}
If you have two streams IObservable<Message> A and IObservable<Message> B, you would use this by doing Observable.Merge(A, B).Sequenced(msg => msg.SequenceNumber, 1, 2).
For your example marble diagram, this would look like the following, where the source column shows the values emitted by Observable.Merge(A, B) and the counts column shows the contents of the SortedDictionary after each step of the algorithm. I am assuming that the "messages" of the original source sequence (without any lost values) is (A,1), (B,2), (C,3), (D,4), (E,5), (F,6) where the second component of each message is its sequence number.
source | counts
-------|-----------
(A,1) | --> emit A
(A,1) | --> skip
(C,3) | (3,(C,1))
(B,2) | (3,(C,1)) --> emit B,C and remove C
(D,4) | --> emit D
(F,6) | (6,(F,1))
(F,6) | (6,(F,2)) --> emit F and remove
A similar question came up a while ago and I have a custom merge operator that when given ordered streams, it merges them in order but doesn't do deduplication.
Edit:
If you can "afford" it, you can use this custom merge and then distinctUntilChanged(Func1) to filter out subsequent messages with the same sequence number.
Observable<Message> messages = SortedMerge.create(
Arrays.asList(src1, src2, src3), (a, b) -> Long.compare(a.id, b.id))
.distinctUntilChanged(v -> v.id);
I would like to do a little project to do some calculation and add the calculated results in listbox.
My code:
int SumLoop(int lowLimit, int highLimit)
{
int idx;
int totalSum = 0;
for (idx = lowLimit; idx <= highLimit; idx = idx + 1)
{
totalSum += idx;
}
return totalSum;
}
private void button1_Click(object sender, EventArgs e)
{
var test2 = Observable.Interval(TimeSpan.FromMilliseconds(1000)).Select(x=>(int)x).Take(10);
test2.Subscribe(n =>
{
this.BeginInvoke(new Action(() =>
{
listBox1.Items.Add("input:" + n);
listBox1.Items.Add("result:" + SumLoop(n,99900000));
}));
});
}
The result:
input:0
result:376307504
(stop a while)
input:1
result:376307504
(stop a while)
input:2
result:376307503
(stop a while)
input:3
result:376307501
(stop a while)
....
...
..
.
input:"9
result:376307468
If i would like to modify the interval constant from 1000 --> 10,
var test2 = Observable.Interval(TimeSpan.FromMilliseconds(10)).Select(x=>(int)x).Take(10);
The displaying behavior becomes different. The listbox will display all inputs and results just a shot. It seems that it waits all results to complete and then display everything to listbox. Why?
If i would like to keep using this constant (interval:10) and dont want to display everything just a shot. I want to display "Input :0" -->wait for calculation-->display "result:376307504"....
So, how can i do this?
Thankx for your help.
If I understand you correctly you're wanting to run the sum loop off the UI thread, here's how you would do that:
Observable
.Interval(TimeSpan.FromMilliseconds(1000))
.Select(x => (int)x)
.Select(x => SumLoop(x, 99900000))
.Take(10)
.ObserveOn(listBox1) // or ObserveOnDispatcher() if you're using WPF
.Subscribe(r => {
listBox1.Items.Add("result:" + r);
});
You should see the results trickle in on an interval of 10ms + ~500ms.
Instead of doing control.Invoke/control.BeginInvoke, you'll want to call .ObserveOnDispatcher() to get your action invoked on the UI thread:
Observable
.Interval(TimeSpan.FromMilliseconds(1000))
.Select(x=>(int)x)
.Take(10)
.Subscribe(x => {
listBox1.Items.Add("input:" + x);
listBox1.Items.Add("result:" + SumLoop(x, 99900000));
});
You said that if you change the interval from 1000 ms to 10ms, you observe different behavior.
The listbox will display all inputs and results just a shot.
I suspect this is because 10ms is so fast, all the actions you're executing are queued up. The UI thread comes around to execute them, and wham, executes everything that's queued.
In contrast, posting them every 1000ms (one second) allows the UI thread to execute one, rest, execute another one, rest, etc.
I am trying to use Reactive Extensions to throttle PropertyChanged notifications. There are examples of doing this using GroupBy, but with one Subscription created for each PropertyName.
I want to handle the PropertyChanged event for all properties, and I need to Throttle those events for each PropertyName.
This is what I have so far, but it causes a deadlock.
ValuesPropertyChanged = Observable.FromEventPattern<PropertyChangedEventArgs>(value, "PropertyChanged")
.GroupBy(o => o.EventArgs.PropertyName)
.First()
.Throttle(TimeSpan.FromSeconds(2))
.Subscribe(args => HandlePropertyChanged(args.EventArgs.PropertyName));
The deadlock happens in the call to .First().
It still locks if I change that line to:
.Select(o => o.First())
I have also tried
.Select(o => o.FirstAsync())
The examples for GroupBy here look pretty concise, but I am incapable of wrapping my head around converting these examples to my solution.
Why does this cause a deadlock, and what should I do to make this work?
I think this might be what you're after:
// assume MyObj : INotifyPropertyChanged, naturally
var value = new MyObj();
Action<string> HandlePropertyChanged =
name => Console.WriteLine("Got a change for name:" + name);
// The query
var valuesPropertyChanged =
// create from event stream
from propChange in Observable.FromEventPattern<PropertyChangedEventArgs>(
value,
"PropertyChanged")
// group events by property name
group propChange by propChange.EventArgs.PropertyName into batchByName
// Throttle the resulting batch
from throttledByName in batchByName.Throttle(TimeSpan.FromSeconds(1))
// then select each item of the "throttled output"
select throttledByName;
valuesPropertyChanged.Subscribe(args =>
HandlePropertyChanged(args.EventArgs.PropertyName));
for(int i=0;i<10;i++)
{
value.Value1 = i.ToString();
value.Value2 = (i-1).ToString();
}
Output:
Got a change for name:Value2
Got a change for name:Value1
Here is the same but with extension methods:
var valuesPropertyChanged =
Observable.FromEventPattern<PropertyChangedEventArgs>(
_vm,
"PropertyChanged")
.GroupBy(propchange => propchange.EventArgs.PropertyName)
.Select(o => o.Throttle(TimeSpan.FromSeconds(1)))
.Merge();
I have two value streams and one selector stream and I'd like to produce a result stream that alternates between the value streams based on the selector. The code below gives the right result, but I don't like it.
Does anyone have anything neater?
var valueStreamA = new BehaviorSubject<int>(0);
var valueStreamB = new BehaviorSubject<int>(100);
var selectorStream = new BehaviorSubject<bool>(true);
var filteredA = valueStreamA .CombineLatest(selectorStream, (a, c) => new { A = a, C = c })
.Where(ac => ac.C)
.Select(ac => ac.A);
var filteredB = valueStreamB.CombineLatest(selectorStream, (b, c) => new { B = b, C = c })
.Where(bc => !bc.C)
.Select(bc => bc.B);
var result = Observable.Merge(filteredA, filteredB);
result.Subscribe(Console.WriteLine);
valueStreamA.OnNext(1);
valueStreamB.OnNext(101);
selectorStream.OnNext(false);
valueStreamA.OnNext(2);
valueStreamB.OnNext(102);
selectorStream.OnNext(true);
This productes the following output:
0
1
101
102
2
I'd do something like this:
var a = new BehaviorSubject<int>(0);
var b = new BehaviorSubject<int>(100);
var c = new BehaviorSubject<bool>(true);
var valueStreamA = a as IObservable<int>;
var valueStreamB = b as IObservable<int>;
var selector = c as IObservable<bool>;
var result = selector
// for every change in the selector...
.DistinctUntilChanged()
// select one of the two value streams
.Select(change => change ? valueStreamA : valueStreamB)
// and flatten the resulting wrapped observable
.Switch();
result.Subscribe(Console.WriteLine);
a.OnNext(1);
b.OnNext(101);
c.OnNext(false);
a.OnNext(2);
b.OnNext(102);
c.OnNext(true);
Could do something like:
var xs = Observable.Interval(TimeSpan.FromSeconds(1)).Select(_ => Feeds.Xs);
var ys = Observable.Interval(TimeSpan.FromSeconds(1)).Select(_ => Feeds.Ys);
var selectorSubject = new Subject<Feeds>();
var query = from selector in selectorSubject
select from merged in xs.Merge(ys)
where merged == selector
select merged;
query.Switch().Subscribe(Console.WriteLine);
OnNext into your 'selectorSubject' to change it.
There are a few differences to your example, but easy to get around:
Your question involved a selector of type bool, whereas I have been lazy and reused the Feeds enum in order to allow me to do an easy equality check (where merged == selector).
You of course could simply do (where selector ? merged == Xs : merged == Ys), or something like that to evaluate each merged item and discard ones you don't care about (depending on your selector).
Specifically, you would probably want to select not just the integer, but an identifier of the feed. Consider using something like Tuple.Create(), so you get that info with each update:
{A - 1}, {B - 101} etc. Your where can then do:
where selector ? merged.Item1 == A : merged.Item1 == B //this maps 'true' to feed A
I also used a Switch, which will cause my sample streams to restart because they are not published.
You probably want to publish yours and Connect them (make them 'hot'), so a Switch like mine doesn't cause any new side effects in the subscription. You have a subject (which is hot), but the 'behaviour' part will replace the value you passed into the constructor. Publishing and connecting would prevent that.
Shout if you are still confused. This isn't a full answer, but might give you enough to think about.
Howard.
Now much closer to your original question:
void Main()
{
var valueStreamA = new BehaviorSubject<int>(0);
var valueStreamB = new BehaviorSubject<int>(100);
var selectorStreamA = valueStreamA.Select(id => Tuple.Create("A", id)).Publish();
var selectorStreamB = valueStreamB.Select(id => Tuple.Create("B", id)).Publish();
var selectorStream = new BehaviorSubject<bool>(true);
var query = from selector in selectorStream
select from merged in selectorStreamA.Merge(selectorStreamB)
where selector == true ? merged.Item1 == "A" : merged.Item1 == "B"
select merged.Item2;
query.Switch().Subscribe(Console.WriteLine);
selectorStreamA.Connect();
selectorStreamB.Connect();
//First we get 0 output (because we are already using stream A, and it has a first value)
valueStreamA.OnNext(1); //This is output, because our selector remains as 'A'
valueStreamB.OnNext(101); //This is ignored - because we don't take from B
selectorStream.OnNext(false); //Switch to B
valueStreamA.OnNext(2); //Ignored - we are now using B only
valueStreamB.OnNext(102); //This is output
selectorStream.OnNext(true); //Switch back to A.
}
Outputs:
0
1
102