Search code examples
wpfreactiveui

Reactive.UI: How to return to main UI thread from an async command


I' exploring ReactiveUI (with WPF) and made a minimal example which I don't get to work. The Command Command creates a string which should be assigned to the property Result. The property is bound in the UI to a TextBlock, the Command to a Button.

public class MainWindowViewModel : ReactiveObject
{
    public MainWindowViewModel()
    {
        this.Command = ReactiveCommand.CreateFromTask(this.CommandExecutedAsync);
        this.result = this.Command
            .ObserveOn(RxApp.MainThreadScheduler)
            .ToProperty(this, x => x.Result);
    }

    public ReactiveCommand<Unit, string> Command { get; }

    private ObservableAsPropertyHelper<string> result;

    public string Result => this.result.Value;

    private async Task<string> CommandExecutedAsync()
    {
        await Task.Delay(1000);

        return DateTimeOffset.UtcNow.ToString();
    }
}

So far so good. If I'm pressing the button the command is executed but when the property Result is set an exception is raised b/c I'm not in the main thread.

I understand that this is a problem but I've thought the ObserveOn(RxApp.MainThreadScheduler) would have put me there. I've also tried to inject the scheduler in ToProperty but this didn't change the outcome.

Edit: Adding execption details:

System.InvalidOperationException
  HResult=0x80131509
  Message=The calling thread cannot access this object because a different thread owns it.
  Source=WindowsBase
  StackTrace:
   at System.Windows.Threading.Dispatcher.VerifyAccess()
   at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
   at System.Windows.Controls.Primitives.ButtonBase.get_Command()
   at System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
   at System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e)
   at System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
   at System.Reactive.AnonymousSafeObserver`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/AnonymousSafeObserver.cs:line 44
   at System.Reactive.Subjects.FastImmediateObserver`1.EnsureActive(Int32 count) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 863
   at System.Reactive.Subjects.ReplaySubject`1.ReplayBase.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 272
   at System.Reactive.Linq.ObservableImpl.CombineLatest`3._.SecondObserver.OnNext(TSecond value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/CombineLatest.cs:line 177
   at System.Reactive.Subjects.FastImmediateObserver`1.EnsureActive(Int32 count) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 863
   at System.Reactive.Subjects.ReplaySubject`1.ReplayBase.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 272
   at System.Reactive.SafeObserver`1.WrappingSafeObserver.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Internal/SafeObserver.cs:line 31
   at System.Reactive.ObserveOnObserverLongRunning`1.Drain() in /_/Rx.NET/Source/src/System.Reactive/Internal/ScheduledObserver.cs:line 735
   at System.Threading.Thread.StartHelper.Callback(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Thread.StartCallback()

Also I've pushed a minimal SLN to github with the code: https://github.com/wgross/wpf-reactive.git


Solution

  • Your case has been tricky for me. I made some tests, and it looks like you didn't add the ReactiveUi.WPF NuGet package.

    Side notes (if you are learning ReactiveUi, you will need this):

    • you are using binding in the XAML way - you can do that, but you should be aware that ReactiveUi has its binding system, which is very useful.
    • please also look at how to make views for ReactiveUi controls (classes ReactiveWindow, ReactiveControl).