在没有视图的视图模型中处理异常(在反应式 UI 中)



我目前正在使用 ReactiveUI 在 C# 中构建一个包含许多视图/视图模型的应用程序。其中一些视图模型以预设的间隔执行网络请求。这些网络请求可能随时失败。我按如下方式实现了这一点:

public ReactiveCommand<Unit, IReactiveList<IJob>> RefreshJobList { get; }
public Interaction<Exception, Unit> RefreshError { get; } = new Interaction<Exception, Unit>();
...
RefreshJobList = ReactiveCommand.CreateFromTask(() => DoNetworkRequest());
RefreshJobList.ThrownExceptions.Subscribe(ex =>
{
log.Error("Failed to retrieve job list from server.", ex);
RefreshError.Handle(ex).Subscribe();
});
Observable.Interval(TimeSpan.FromMilliseconds(300)).Select(x => Unit.Default).InvokeCommand(RefreshJobList);

在相应的视图中,我按如下方式处理异常:

this.WhenActivated(d => d(
this.ViewModel.RefreshError.RegisterHandler(interaction =>
{
MessageBox.Show("Failed to load joblist.", "Error", MessageBoxButton.OK);
interaction.SetOutput(new Unit());
})
));

这工作正常,除非视图模型未与视图关联。我的应用程序使用选项卡,当用户切换到其他选项卡时,以前的视图将被销毁。这会使视图模型在没有视图的情况下运行,仍在发出请求。然后,当RefreshJobList发生错误时,没有处理程序与RefreshError相关联,ReactiveUI抛出UnhandledInteractionError并且我的应用程序崩溃。

我不知道如何干净利落地处理这个问题。我的第一个想法是暂停视图模型,直到附加视图,这也将节省网络流量。但是,我似乎无法检查视图是否附加到视图模型。有什么想法吗?

如果视图实现IViewFor并且视图模型实现ISupportsActivation则可以在离开视图时将订阅释放到ThrownExceptions

// In your VM
this.WhenActivated(d=>
{
RefreshJobList
.ThrownExceptions
.Do(ex => log.Error("Failed to retrieve job list from server.", ex))
.SelectMany(ex => RefreshError.Handle(ex))
.Subscribe()
.DisposeWith(d); // This will dispose of the subscription when the view model is deactivated
});

这将使ReactiveCommand在视图模型未处于活动状态时发生异常时引发异常。要解决此问题,您可以在停用视图模型时停止正在运行的操作。或者,您可以按照 jamie 的建议捕获异常:RefreshError.Handle(ex).Catch(ex => Observable.Empty<Exception>())

为什么不能在视图模型中使用 WhenActivated 扩展来"停止"/处置在处置视图时不应再发挥作用的可观察量?

我相信您始终可以从订阅中处理的交互中捕获异常,只需添加一个 OnError 处理程序即可。

解决方案的关键是使用WhenActivated,由 ViewModel 上的ISupportsActivation提供。

我现在在 de viewmodel 中使用以下代码:

public class ViewModel : ReactiveObject, ISupportsActivation
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public ReactiveCommand<Unit, IReactiveList<IJob>> RefreshJobList { get; }
public Interaction<Exception, Unit> RefreshError { get; } = new Interaction<Exception, Unit>();
...
public ViewModel(){
RefreshJobList = ReactiveCommand.CreateFromTask(() => DoNetworkRequest());
RefreshJobList.ThrownExceptions.Subscribe(ex =>
{
log.Error("Failed to retrieve job list from server.", ex);
RefreshError.Handle(ex).Subscribe();
});
this.WhenActivated(d => d(
Observable.Interval(TimeSpan.FromMilliseconds(300)).Select(x => Unit.Default).InvokeCommand(RefreshJobList)
));
}
}

这非常有效。

最新更新