Answered DispatcherScheduler.Instance Anomaly

  • Friday, February 01, 2013 8:49 AM
     
      Has Code

    Here is an interesting one: 

    .NET4 WPF, RX2.0 

            private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
            {
                Observable.Timer(new TimeSpan(0,0,1))
                            .Do(_ => { var x = DispatcherScheduler.Instance; })
                            .SelectMany(_ => DoBackground())
                            .Subscribe();
            }
    
            private IObservable<Unit> DoBackground()
            {
                return Observable.Timer(new TimeSpan(0, 0, 1))
                                 .Do(_ => Debug.WriteLine("hi"))
                                 .ObserveOnDispatcher()
                                 .Do(_ => Debug.WriteLine("there"))
                                 .Select(_ => Unit.Default);
    
            }

    The above code will only print "hi", i.e anything past the ObserveOnDispatcher() does not execute.

    If you comment the line 

                            .Do(_ => { var x = DispatcherScheduler.Instance; })

    out, then you will receive an exception "The current thread has no Dispatcher associated with it." - which is probably the more correct behaviour.

    I know that use of DispatcherScheduler.Instance is deprecated so that's fine, it's just bizarre that even looking at it without doing anything with it can change behaviour? 

All Replies

  • Friday, February 01, 2013 9:25 AM
     
     

    So yeah, pre-empting the answers here - I know we should be using DispatcherSheduler.Current (which throws if not created on the UI thread), and injecting a scheduler provider (we know ObserveOnDispatcher uses DispatcherScheduler.Current.Dispatcher under the covers, but my 2 questions are: 

    • Just for our understanding, why is the 2nd ObserveOnDispatcher locking the thread so that 'there' isnt written? 
    • Why isnt ObserveOnDispatcher just coded to use Application.Current.Dispatcher?
  • Friday, February 01, 2013 6:27 PM
     
     Answered Has Code

    Hi,

    DispatcherScheduler.Instance is mimicking the behavior of WPF.

    The same behavior occurs if you simply read the Dispatcher.CurrentDispatcher property directly.

    .Do(_ => { var x = System.Windows.Threading.Dispatcher.CurrentDispatcher; })

    WPF lazily creates a dispatcher on the current thread when one is requested.  It's your responsibility to ensure that you don't request a dispatcher on a pooled thread in WPF.

    As znite pointed out, DispatcherScheduler.Instance is deprecated in Rx 2.0.  You should use DispatcherScheduler.Current instead, which throws an exception in your example.

    @znite:

    > why is the 2nd ObserveOnDispatcher locking the thread so that 'there' isnt written? 

    The reason that "there" isn't written is not because ObserveOnDispatcher is locking the thread.  It enqueues the remainder of the query, yet it's simply never executed.  It's not executed because it's being scheduled on the dispatcher of the current thread that is executing the call to DoBackground, which is a pooled thread.  The dispatcher is created automatically on the pooled thread when it's requested by Do in the parent query; however, just because a thread-affine dispatcher is created doesn't mean that the thread is actively processing dispatched actions; i.e., there's no message loop.  Although, there could be a loop if you were to call Dispatcher.Run, but there's no good place to call it within the query.  Note that calling it in Subscribe or after SelectMany doesn't work because the observer is executed by the second Timer, which uses yet another pooled thread.  (Edit: Removed invalid comment.)  And calling it before SelectMany doesn't work either because it would block the query, defeating the purpose of SelectMany!

    > Why isnt ObserveOnDispatcher just coded to use Application.Current.Dispatcher?

    Perhaps because it's not necessarily invalid to have multiple UI threads in a single application.  In other words, WPF supports creating a dispatcher on a background thread and running a secondary GUI.  Dispatchers are thread-affine in WPF.  So it makes sense that ObserveOnDispatcher uses the current thread's dispatcher, by default.  You can always override the default behavior with ObserveOn:

    .ObserveOn(Application.Current.Dispatcher)

    - Dave


    http://davesexton.com/blog

    • Edited by Dave Sexton Friday, February 01, 2013 6:30 PM Clarifaction about Dispatcher.Run usage
    • Marked As Answer by HamishG Monday, February 04, 2013 8:24 AM
    •