来自 Observable.Timer 的 InvokeCommand 导致跨线程问题

InvokeCommand from Observable.Timer causes cross-thread issue

我有一个 ReactiveCommand 可以刷新数据并绑定到 XAML 中的一个 Button。该功能运行良好,但我还想在计时器上执行命令。

我有以下代码 - SetupAutoRefresh 是从我的 VM 中的 ctor 调用的,但是当 Observable 触发时,我收到一条异常消息:"The calling thread cannot access this object because a different thread owns it."

虚拟机:

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"/>

调试输出提供了这个堆栈跟踪:

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

我已经尝试了很多不同的变体来尝试在 RxApp.MainThreadScheduler 上安排这个但没有任何乐趣 - ObserveOn、SubscribeOn、设置输出调度程序... none 其中我有很多希望无论如何。

感觉我在这里遗漏了一些明显的东西,但整个下午我的头都在撞砖墙。当然这种情况在 RxUI 中是可能的吗?

Refresh 方法在后台线程上运行;您不能在该方法中修改数据绑定属性。

试试这个:

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

现在,您的 ReactiveCommand 只需获取新数据,然后 returns 在 Subscribe 中的 UI 线程上将其发送给您 :)

解决了问题 - 看起来需要在 UI 线程上创建 Observable。我在原来的 post 中错过了它,但是 SetupAutoRefresh 方法是从另一个异步方法调用的,该方法在之前的等待期间切换了上下文。