简体   繁体   中英

Unexpected behaviour when merging two IObservables

I have the following situation in my recent "Play With Rx"-project:

class Program
{
    static void Main(string[] args)
    {
        var observable1 = Observable.Create<int>(
               (Func<IObserver<int>, IDisposable>)GenerateSequence);
        var observable2 = Observable.Create<int>(
               (Func<IObserver<int>, IDisposable>)GenerateSequence);
        var merged = observable1.Merge(observable2);

        observable1.Subscribe(i => Console.WriteLine("1: " + i.ToString()));
        observable2.Subscribe(i => Console.WriteLine("2: " + i.ToString()));
        merged.Subscribe(i => Console.WriteLine("Merged: " + i.ToString()));

        Console.ReadLine();
    }

    private static int count = 0;

    private static IDisposable GenerateSequence(IObserver<int> observer)
    {
        ThreadPool.QueueUserWorkItem((o) =>
        {
            while (true)
            {
                observer.OnNext(count++);
                Thread.Sleep(500);
            }
        });
        return Disposable.Empty;
    }
}

Now, I expected to see something like

1: 0
2: 1
Merged: 0
Merged: 1
1: 2
2: 3
Merged: 2
Merged: 3

Instead I am seeing

1: 0
2: 1
Merged: 2
Merged: 3
1: 4
2: 5
Merged: 6
Merged: 7

If I replace the loop by

while (true)
{
    observer.OnNext(r.Next(0, 1000));
    Thread.Sleep(500);
}

for a either static or local instance r of Random, the merged sequence has other numbers in it that the two seperate sequences!

I do not see how count++ or r.Next(0, 1000) can be executed multiple times from one call of observer.OnNext(...) . What about Merge do I not understand?

PS: I've tried to eliminate race conditions by locks or seperating the loop times of the two threads, but the result was unchanged, so I left these attempts out of the question.

Edit: It seems that GenerateSequence is called 4 times, so that 4 threads are spun up to increment count . While this explains what I see, I do not understand why it should be so.

  • When you subscribe to observable1 , you subscribe to Observable.Create(GenerateSequence) , which calls GenerateSequence and starts a loop.
  • When you subscribe to observable2 , you subscribe to Observable.Create(GenerateSequence) , which calls GenerateSequence and starts a loop.
  • When you subscribe to merged , you subscribe to Observable.Merge(observable1, observable2) , which subscribes to observable1 and observable2 . We saw in the first two points what happens when you do each of those.

The net result is four calls to GenerateSequence .

To get an effect pretty close to you're looking for, you need to look at Publish() :

var observable1 = Observable
    .Create<int>((Func<IObserver<int>, IDisposable>)GenerateSequence)
    .Publish();
var observable2 = Observable
    .Create<int>((Func<IObserver<int>, IDisposable>)GenerateSequence)
    .Publish();
var merged = observable1.Merge(observable2);

observable1.Subscribe(i => Console.WriteLine("1: " + i.ToString()));
observable2.Subscribe(i => Console.WriteLine("2: " + i.ToString()));
merged.Subscribe(i => Console.WriteLine("Merged: " + i.ToString()));

observable1.Connect();
observable2.Connect();

observable1 and observable2 are now of type IConnectableObservable , which means they hold off subscribing to their underlying IObservable ( Observable.Create in your case) until they have Connect called.

Output

1: 0
Merged: 0
2: 1
Merged: 1
1: 2
Merged: 2
2: 3
Merged: 3
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM