【问题标题】:InvokeCommand from Observable.Timer causes cross-thread issue来自 Observable.Timer 的 InvokeCommand 导致跨线程问题
【发布时间】:2017-06-02 16:51:08
【问题描述】:

我有一个 ReactiveCommand 刷新数据并绑定到 XAML 中的按钮。该功能工作正常,但我也想在计时器上执行命令。

我有以下代码 - 从我的 VM 中的 ctor 调用 SetupAutoRefresh,但是当 Observable 触发时,我收到一条异常消息:“调用线程无法访问此对象,因为不同的线程拥有它。”

虚拟机:

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

调试输出提供此堆栈跟踪:

在 System.Windows.Threading.Dispatcher.VerifyAccess() 在 System.Windows.DependencyObject.GetValue(DependencyProperty dp) 在 System.Windows.Controls.Primitives.ButtonBase.get_Command() 在 System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute() 在 System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(对象 > 发件人,EventArgs e) 在 System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(对象发送者,EventArgs e) 在 C:\projects\reactiveui\src\ReactiveUI\ReactiveCommand.cs:line 628 中的 ReactiveUI.ReactiveCommand.OnCanExecuteChanged() 处

我尝试了许多不同的变体,试图在 RxApp.MainThreadScheduler 上安排它,但没有任何乐趣 - ObserveOn、SubscribeOn、设置输出调度器......反正我对这些都没有太大希望。

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

【问题讨论】:

    标签: wpf reactiveui


    【解决方案1】:

    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 只需获取新数据,并在Subscribe 内的 UI 线程上将其返回给您 :)

    【讨论】:

    • 谢谢,我也试过这个 - 但它不起作用,因为它没有解决根本问题。它抱怨的不是数据绑定属性,而是绑定到按钮的命令。特别是在命令执行期间 CanExecute 发生更改(可能为 false)时。请参阅我原始帖子中的堆栈跟踪。
    • 好的,我已经复制了您的原始代码并构建了依赖项(MainViewModel.csMainWindow.xaml)。这对我来说非常有效;我唯一改变的是在SetupAutoRefresh 中交换ObservableReactiveCommand 创建顺序,否则它会崩溃。我还用Add 替换了Merge。也许这有帮助?
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-13
      • 1970-01-01
      • 1970-01-01
      • 2012-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多