0
votes

I have a ReactiveCommand that refreshes data and is bound to a Button in XAML. The functionality works fine, but I also want to execute the command on a timer.

I have the following code - SetupAutoRefresh is called from the ctor in my VM, but when the Observable fires, I get an exception with the message: "The calling thread cannot access this object because a different thread owns it."

VM:

private void SetupAutoRefresh() {
    Observable.Timer(TimeSpan.FromSeconds(5))
            .Select(_ => Unit.Default)
            .ObserveOn(RxApp.MainThreadScheduler)
            .InvokeCommand(RefreshData);

    RefreshData = ReactiveCommand.CreateFromTask(Refresh);
}

private async Task Refresh()
{
    var updatedData = await _repository.GetAll();
    Data.Merge(updatedData);
}

private ReactiveCommand<Unit, Unit> _refreshData;
public ReactiveCommand<Unit, Unit> RefreshData
{
    get { return _refreshData; }
    set { this.RaiseAndSetIfChanged(ref _refreshData, value); }
}

private IReactiveList<Model> _data;
public IReactiveList<Model> Data
{
    get { return _data; }
    set { this.RaiseAndSetIfChanged(ref _data, value); }
}

XAML:

<Button Grid.Column="2"
        Command="{Binding RefreshData}"
        Style="{StaticResource ToolbarButtonTheme}"
        Content="{StaticResource RefreshToolbarIcon}"
        ToolTip="Refresh Search"/>

Debug output provides this 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 ReactiveUI.ReactiveCommand.OnCanExecuteChanged() in C:\projects\reactiveui\src\ReactiveUI\ReactiveCommand.cs:line 628

I've tried many different variations of attempting to schedule this on the RxApp.MainThreadScheduler but without any joy - ObserveOn, SubscribeOn, setting the output scheduler... none of which I had much hope for anyway.

Feel like I'm missing something obvious here, but have been banging my head against a brick wall for the whole afternoon. Surely this scenario is possible in RxUI?

2

2 Answers

0
votes

The Refresh method runs on a background thread; you can't modify databound properties within that method.

Try this:

private void SetupAutoRefresh() {
    Observable.Timer(TimeSpan.FromSeconds(5))
            .Select(_ => Unit.Default)
            // remove ObserveOn here; the Command will run on the background
            .InvokeCommand(RefreshData);

    RefreshData = ReactiveCommand.CreateFromTask(Refresh);
    // RefreshData.Subscribe is guaranteed to run on the UI thread
    RefreshData.Subscribe(listOfModels => Data.Merge(listOfModels))
}

private async Task Refresh()
{
    // all this method does is deliver a list of models
    return await _repository.GetAll();
}

// return IEnumerable<Model> from the command
public ReactiveCommand<Unit, IEnumerable<Model>> RefreshData

Now, your ReactiveCommand simply fetches the new data, and returns it to you on the UI thread within Subscribe :)

0
votes

Figured out the issue - looks like the Observable needed to be created on the UI thread. I missed it from the original post, but the SetupAutoRefresh method had been called from another async method, which had switched context during a prior await.