2

in my example below where I have 2 subjects and the _primarySubject is always called before the _secondarySubject is the subscriber guaranteed to receive the primary callback before the secondary callback? Test simple test seems to say yes or is this by fluke?

And if this is by fluke how can i change the code to guarantee order as I describe here.

Thanks a lot.

public class EventGenerator
{
    ISubject<string> _primarySubject = new Subject<string>();
    ISubject<int> _secondarySubject = new Subject<int>();
    private int i;
    public void SendEvent(string message)
    {
        _primarySubject.OnNext(message);
        _secondarySubject.OnNext(i++);
    }

    public IObservable<string> GetPrimaryStream()
    {
        return _primarySubject;
    }

    public IObservable<int> GetSecondaryStream()
    {
        return _secondarySubject;
    }
}

public class EventSubscriber
{
    private static IScheduler StaticFakeGUIThread = new EventLoopScheduler(x => new Thread(x) { Name = "GUIThread" });

    private readonly int _id;
    public EventSubscriber(int id, EventGenerator eventGenerator)
    {
        _id = id;
        eventGenerator.GetPrimaryStream().ObserveOn(StaticFakeGUIThread).Subscribe(OnPrimaryEvent);
        eventGenerator.GetSecondaryStream().ObserveOn(StaticFakeGUIThread).Subscribe(OnSecondaryEvent);
    }

    private void OnPrimaryEvent(String eventMsg)
    {
        string msg = eventMsg;
    }

    private void OnSecondaryEvent(int i)
    {
        int msg = i;
    }
}

[TestFixture]
public class EventGeneratorTest
{
    [Test]
    public void Test()
    {
        EventGenerator eg = new EventGenerator();

        EventSubscriber s1 = new EventSubscriber(1,eg);
        EventSubscriber s2 = new EventSubscriber(2, eg);

        eg.SendEvent("A");
        eg.SendEvent("B");
    }
}
CodingHero
  • 2,865
  • 6
  • 29
  • 42

3 Answers3

2

In general, Rx does not make very many guarantees about the relative ordering of different observable streams. Ordering is dependent upon many factors beyond the control of RX.

In your specific case, the ordering is guaranteed:

  1. Since you trigger your subjects synchronously in order, it is guaranteed that their immediate subscriptions (.ObserveOn(StaticFakeUIThread)) are triggered in order.
  2. Since you use the same scheduler instance for both calls to ObserveOn, it is guaranteed that the observation of the primary stream event will be scheduled before the observation of the secondary stream event and both observations will be scheduled with the same scheduler
  3. Since your scheduler is an EventLoopScheduler, it is guaranteed that it will not run the scheduled tasks concurrently, but will instead run them in the order in which they are scheduled.

Thus your observations will indeed run in order.

Replace EventLoopScheduler with TaskPoolScheduler, ThreadPoolScheduler, or Scheduler.Default and there will no longer be any guarantees on the relative ordering of the two streams because those schedulers are allowed to run the two scheduled tasks concurrenly.

Brandon
  • 38,310
  • 8
  • 82
  • 87
1

It all depends on what you mean by "guaranteed". The current implementation of Rx and the code as written is such that the invocation order will always be as you have observed - however, there are no published rules governing that behaviour and guarantees for it.

The way your code is written offers no particular advantage to using two subjects - or indeed the two subscriptions, since the handlers are invoked on the same thread. You can get the guarantee you need by just using a single subject that combines the events - use a custom type for this. For expediency I've used a Tuple:

ISubject<Tuple<string,int>> _subject = new Subject<Tuple<string,int>>();
private int i;
public void SendEvent(string message)
{
    _subject.OnNext(Tuple.Create(message,i++));
}

And your subscription:

private void OnEvent(Tuple<String,int> eventMsg)
{
    string msgText = eventMsg.Item1;
    int msgNum = eventMsg.Item2;
}

This gives you your guarantee regardless of Scheduler etc. I suspect there must be some more compelling reason for your question - but otherwise this is more efficient.

If you are just wanting to use a counter btw, there are overloads of Select that introduce a 0 based event index counter for you.

James World
  • 29,019
  • 9
  • 86
  • 120
0

In theory everything should work in order. In practice there is a bug in EventLoopScheduler which breaks order if the queue is starving.

see the issue here https://github.com/Reactive-Extensions/Rx.NET/issues/455