toddmo
toddmo

Reputation: 22406

Rx Example not working

I'm trying to follow along with Jonathan Worthington's airport announcement example in An Event-driven and Reactive Future

It compiles.

The problem: SayGateChange is never called. I'm new to Rx. I must be leaving something out. What I have here is his code as exactly as I could transcribe it. Sadly, there is no source available online.

AddGateChange is supposed to push a new item onto EventStreams.GateChanged, which in turn is supposed to be watched by Announcer.Announcements, which is supposed to be watched by SayGateChange.

I'm in Windows forms, not WPF, if that makes a difference.

I will gladly put it into a console app or LinqPad if that will make it work.

using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;

public class frmAnnouncements
{

    Announcer _Announcer = new Announcer();

    ObservableCollection<string> Announcements = new ObservableCollection<string>();

    private void frmRx_Load(System.Object sender, System.EventArgs e)
    {
        PopulateAnnouncements();

        AddGateChange();

    }

    private void AddGateChange()
    {
        EventStreams.GateChanged.OnNext(new GateChanged {
            Destination = "DAL",
            FlightCode = 1503
        });
    }

    private void PopulateAnnouncements()
    {
        _Announcer.Announcements.ObserveOnDispatcher().Subscribe(SayGateChange);
    }

    private void SayGateChange(string Message)
    {
        Interaction.MsgBox(Message);
    }

    public class GateChanged
    {
        public string FlightCode;
        public string Destination;
    }

    public class EventStreams
    {
        public static Subject<GateChanged> GateChanged = new Subject<GateChanged>();
    }

    public class Announcer
    {

        public Announcer()
        {
            this.Announcements = EventStreams.GateChanged.Select(e => string.Format("gate change {0} to {1} ", e.FlightCode, e.Destination));
        }


        public IObservable<string> Announcements;
    }
    public frmAnnouncements()
    {
        Load += frmRx_Load;
    }

}

Upvotes: 2

Views: 834

Answers (1)

James World
James World

Reputation: 29776

As @Enigmativity stated, using ObserveOnDispatcher() is a problem - although without looking at Interaction.MsgBox its hard to be 100% certain it's the whole story - I guess it may be in the video, but it's rather long and I didn't watch it all.

The use of ObservableOnDispatcher() suggests you have pulled in the wrong nuget package for Rx:

  • For WPF applications, use rx-xaml (deprecated synonym rx-wpf), which provides the extension method ObserveOnDispatcher()
  • For Winforms applications, use rx-winforms, which provides the extension method overload ObserveOn(Control)

Both Winforms and WPF have a similar design where the user interface runs on a dedicated thread. In Winforms this is known as the "UI Thread" and in WPF as the "Dispatcher". Although the concept is very similar, the implementation is quite different.

ObserveOnDispatcher in WPF will cause the observer notifications OnXXX to be invoked on the dispatcher thread.

In WinForms, where you use ObserveOn(this), the this will generally be the form itself. For any WinForms control, this will locate the control's SynchronizationContext and Post OnXXX notifications to that.

Both overloads are smart in that invocations are direct if you happen to be on the correct Dispatcher thread or UI thread already.

I do seem to remember that WinForms is a lot more tolerant of updating UI off the UI thread - although this problem occurs in WPF too. This isn't a good thing, since it can lead to unpredictable results that are hard to debug. I note that the WinForms MessageBox.Show method, for example, doesn't care which thread it is invoked on since it creates it's own window. In general, use of some form of ObserveOn/ObserveOnDispatcher is always recommended in UI scenarios.

For this reason, it's a good idea to understand how these work in detail. For this, and to learn about the related SubscribeOn, have a look at this question.

I am surprised that you didn't get an informative InvalidOperationException stating that "The current thread has no Dispatcher associated with it." I can only think some other part of your code is swallowing exceptions, or you are using WPF code in your app as well and a Dispatcher had been created associated with the Winforms UI thread. That code behind Interaction.MsgBox is probably to blame for swallowing an error. Either way, I suggest removing rx-xaml to avoid confusion.

Upvotes: 1

Related Questions