Xamarin.Forms+Prism视图模型数据加载方案是否正确?有无更优解?
你的Prism MVVM方案分析与优化建议
咱们先拆解下你现有实现里的几个潜在问题,再分享更简洁的优化思路:
现有方案的待优化点
- 线程安全隐患:
_getObjectsFromIdsTask没有同步保护,要是OnNavigatingTo和RefreshListViewSource同时操作这个变量,可能出现竞态条件,导致重复发起请求或者任务状态判断出错。 async void的风险:虽然你想避免,但RefreshListViewSource是async void——作为命令执行方法,一旦抛出未捕获的异常(哪怕你加了catch,也有漏网的可能),会直接导致应用崩溃,而且这类异常很难追踪。- 冗余的任务状态判断:你对
Task.Status的分支判断其实没必要,直接await任务就够了——如果任务已经完成,await会立刻返回;如果正在运行,会自动等待;如果失败,也能通过IsFaulted判断,不用逐个枚举状态。 - 参数获取笔误:在
OnNavigatingTo里,你写的是parameters.GetValue<string>("Object1"),但参数Key应该是"Object1Id"吧?这会导致你拿不到正确的ID,异步获取数据肯定会出问题。 - 不能删除的“空分支”:你觉得那个“永远不会到达的分支”其实有可能触发——比如页面还没执行
OnNavigatingTo,视图就先触发了刷新命令(比如页面加载时自动调用下拉刷新),这时候_getObjectsFromIdsTask还是null,所以这个分支得保留,但可以优化处理逻辑。
更简洁的实现方式
核心思路是利用Prism的异步命令支持、任务缓存,以及自动触发刷新的逻辑,把代码简化的同时解决上述问题:
优化后的ViewModel代码
//ViewModelBase implements INaviagtionAware and INotifyPropertyChanged public class MainPageViewModel : ViewModelBase { private string _object1Id; private string _object2Id; private Task _loadObjectsTask; private Object1 _object1; private Object2 _object2; private DelegateCommand _refreshListViewSourceCommand; private bool _isRefreshing; public Object1 Object1 { get => _object1; // 当Object1更新时,触发对象就绪检查 set => SetProperty(ref _object1, value, OnObjectsUpdated); } public Object2 Object2 { get => _object2; // 当Object2更新时,触发对象就绪检查 set => SetProperty(ref _object2, value, OnObjectsUpdated); } // 使用异步委托创建命令,避免async void public DelegateCommand RefreshListViewSourceCommand => _refreshListViewSourceCommand ??= new DelegateCommand(async () => await RefreshListViewSourceAsync(), CanExecuteRefreshListViewSource) .ObservesProperty(() => IsRefreshing); public bool IsRefreshing { get => _isRefreshing; set => SetProperty(ref _isRefreshing, value); } public MainPageViewModel(INavigationService navigationService) : base(navigationService) { Title = "Main Page"; } private async Task RefreshListViewSourceAsync() { IsRefreshing = true; try { // 已有完整对象,直接刷新列表 if (Object1 != null && Object2 != null) { PopulateListView(); return; } // 确保加载任务存在,不存在就初始化 _loadObjectsTask ??= LoadObjectsAsync(); await _loadObjectsTask; // 如果任务失败,重置任务并重试一次 if (_loadObjectsTask.IsFaulted) { _loadObjectsTask = LoadObjectsAsync(); await _loadObjectsTask; } } catch (Exception e) { Debug.WriteLine($"加载数据出错: {e.Message}"); } finally { // 不管成功失败,都重置刷新状态 IsRefreshing = false; } } private bool CanExecuteRefreshListViewSource() => !IsRefreshing; private async Task LoadObjectsAsync() { // 模拟异步Web请求获取数据 var object1Task = Task.Delay(100).ContinueWith(_ => new Object1()); var object2Task = Task.Delay(100).ContinueWith(_ => new Object2()); Object1 = await object1Task; Object2 = await object2Task; } private void OnObjectsUpdated() { // 当两个对象都就绪时,自动刷新ListView if (Object1 != null && Object2 != null) { PopulateListView(); } } private void PopulateListView() { // 这里写ListView数据源的填充逻辑 // 比如:ListViewItems = new ObservableCollection<Item>(Object1.Items.Concat(Object2.Items)); } public override void OnNavigatingTo(NavigationParameters parameters) { base.OnNavigatingTo(parameters); if (parameters.ContainsKey("Object1Id") && parameters.ContainsKey("Object2Id")) { // 修正参数Key的笔误,确保拿到正确的ID _object1Id = parameters.GetValue<string>("Object1Id"); _object2Id = parameters.GetValue<string>("Object2Id"); // 初始化加载任务 _loadObjectsTask = LoadObjectsAsync(); } else if (parameters.ContainsKey("Object1") && parameters.ContainsKey("Object2")) { // 直接接收完整对象,自动触发列表刷新 Object1 = parameters.GetValue<Object1>("Object1"); Object2 = parameters.GetValue<Object2>("Object2"); } } }
优化细节说明
- 替换
async void为async Task:Prism的DelegateCommand支持异步委托,这样既符合异步编程规范,又避免了async void的异常风险。 - 简化任务处理:去掉繁琐的
Task.Status判断,直接await任务——不管任务是已完成、正在运行还是失败,都能优雅处理。 - 自动触发刷新:在
Object1和Object2的SetProperty回调里添加OnObjectsUpdated方法,只要两个对象都就绪,就自动刷新ListView,不用等用户手动触发刷新。 - 修复参数笔误:把
"Object1"改成"Object1Id",确保能正确获取导航参数里的ID。 - 线程安全的任务初始化:用
??=运算符确保_loadObjectsTask只会被初始化一次,避免重复发起请求。 - 可靠的状态重置:用
finally块保证IsRefreshing一定会被重置,不会出现UI一直卡在刷新状态的情况。
额外小建议
- 试试
IAsyncCommand:如果你的Prism版本是7.2及以上,可以用IAsyncCommand替代DelegateCommand,它内置了对加载状态的管理,甚至能省去手动维护IsRefreshing的代码。 - 添加加载UI:在视图里加个加载指示器(比如
ActivityIndicator),绑定IsRefreshing属性,让用户知道数据正在加载,体验更好。 - 增强错误提示:可以加个
ErrorMessage属性,当加载失败时显示错误信息,再给用户个重试按钮,提升容错性。
内容的提问来源于stack exchange,提问作者Amr Al Sayed
相关产品推荐
相关产品推荐

