none
Bug with ObserveOn(control) for RX Winforms Support RRS feed

  • Question

  • IObservable<int> o = ....;
    UserControl c = ....;
    
    o.ObserveOn(c).Subscribe( (v) c.Text = v );

    Will fail if an event arrives before the control is created. I have made a safer version of ObserveOn to get around this problem. I hope this is the place to submit bug reports.

            private static Action<R> Bounce<R>(Action<R> action, Control control)
            {
                return (R v) =>
                {
                    if (control.IsHandleCreated)
                    {
                        if (!control.IsDisposed)
                        {
                            if (control.InvokeRequired)
                            {
                                control.Invoke(new Action(() => action(v)));
                            }
                            else
                            {
                                action(v);
                            }
                        }
                    }
                    else
                    {
                        control.HandleCreated += delegate
                        {
                            action(v);
                        };
    
                    }
                };
            }
    
            public static IObservable<T> SafeObserveOn<T>(this IObservable<T> observable, Control control)
            {
                    return Observable.Create( (IObserver<T> observer) => 
                        {
                            return observable.Subscribe(
                                Bounce<T>( (v) =>observer.OnNext(v), control),
                                Bounce<Exception>( (e) =>observer.OnError(e), control));
                        });
    
            }
    
    

    Monday, November 5, 2012 3:11 PM

All replies

  • Thanks for taking the time to contact us. We'll have a look at this case, though it's debatable what one expects to happen in this scenario.

    For example, should an error about the control's invalid state be propagated to the observer? After all, the intent of the query couldn't be met and an event was received. Thus far, we've left the responsibility for proper initialization (i.e. timing the Subscribe call properly in relation to the state of its dependencies, in this case the control) to the user of the library. Using operators like TakeUntil and SkipUntil can work here (e.g. SkipUntil with a FromEventPattern for the HandleCreated event, and a TakeUntil with a FromEventPattern for a destroy event).

    Queuing up undeliverable events is another strategy, at the risk of never draining the queue if the condition is never met. In the code shown above, there's no such explicit queuing, but a bunch of HandleCreated event handlers can pile up and never be removed once the control is created eventually.

    • Edited by Rx team Monday, November 5, 2012 7:49 PM
    Monday, November 5, 2012 7:49 PM
  • The *normal* in my case use case is to bind an observable to a form control such as a textbox control. In this case the only important thing is that I get the most recent value. I use it like so

                public static IDisposable BindToControl<TControl, TValue>(
                    this IObservable<TValue> This,
                    TControl control,
                    Expression<Func<TControl, TValue>> property,
                    Func<TValue> fallbackValue = null)
                    where TControl : Control
                {
                    var subscriptions = new CompositeDisposable();
                    var pn = Reflection.ExpressionToPropertyNames(property);
    
                    // The action to subscribe to the observable
                    var subscription = This.SafeObserveOn(control).Subscribe(
                        onNext: (v) => Reflection.SetValueToPropertyChain(control, pn, v),
                        onError: (Exception e) => {
                            // Just eat the exception. Are
                            // We sure that this is right?? BPH
                            Debug.WriteLine(e.ToString());
                        }
                    );
    
                    // Always unsubscribe when the control
                    // is destroyed
                    control.HandleDestroyed += delegate
                    {
                        subscription.Dispose();
                    };
    
                    return subscription;
                }
    

    In this case I just throw away the error from the stream so propogating an error from ObserveOn(control) would be fine as it would be a noop. Perhaps some type of policy driven behavior would be appropriate.

     
            public static IObservable<T> SafeObserveOn<T>(
    this IObservable<T> observable, Control control,
    ObserveOnControlPolicyEnum policy = ObserveOnControlPolicyEnum.MostRecent){...}

    You are right I am building up a nest of HandleCreatedEvents which is not great. I will fix that.

    Tuesday, November 6, 2012 6:22 AM
  • This version only preserves the last value set.

            private static void InvokeIfRequired(this Control control, Action action)
            {
                if (control.InvokeRequired)
                {
                    control.Invoke(action);
                }
                else
                {
                    action();
                }
            }
    
            private static Action<R> Bounce<R>(Action<R> action, Control control)
            {
                R memory = default(R);
                bool memorySet = false;
    
                if (!control.IsHandleCreated)
                {
                    control.HandleCreated += delegate
                    {
                        lock (control)
                        {
                            if (memorySet)
                            {
                                action(memory);
                                memorySet = false;
                            }
                        }
                    };
                }
    
                return (R v) =>
                {
                    if (control.IsHandleCreated)
                    {
                        if (!control.IsDisposed)
                        {
                            control.InvokeIfRequired(() => action(v));
                        }
                    }
                    else
                    {
                        lock (control)
                        {
                            memory = v;
                            memorySet = true;
                        }
                    }
                };
            }
    
            public enum SafeObserveOnPolicyEnum
            {
                MostRecent = 0
            }
    
            public static IObservable<T> SafeObserveOn<T>(
                this IObservable<T> observable, 
                Control control, 
                SafeObserveOnPolicyEnum policy = SafeObserveOnPolicyEnum.MostRecent )
            {
                    return Observable.Create( (IObserver<T> observer) => 
                        {
                            return observable.Subscribe(
                                Bounce<T>( (v) =>observer.OnNext(v), control),
                                Bounce<Exception>( (e) =>observer.OnError(e), control));
                        });
    
            }
    


    Tuesday, November 6, 2012 6:45 AM