You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin.Forms+Prism视图模型数据加载方案是否正确?有无更优解?

你的Prism MVVM方案分析与优化建议

咱们先拆解下你现有实现里的几个潜在问题,再分享更简洁的优化思路:

现有方案的待优化点

  1. 线程安全隐患:_getObjectsFromIdsTask没有同步保护,要是OnNavigatingTo和RefreshListViewSource同时操作这个变量,可能出现竞态条件,导致重复发起请求或者任务状态判断出错。
  2. async void的风险:虽然你想避免,但RefreshListViewSource是async void——作为命令执行方法,一旦抛出未捕获的异常(哪怕你加了catch,也有漏网的可能),会直接导致应用崩溃,而且这类异常很难追踪。
  3. 冗余的任务状态判断:你对Task.Status的分支判断其实没必要,直接await任务就够了——如果任务已经完成,await会立刻返回;如果正在运行,会自动等待;如果失败,也能通过IsFaulted判断,不用逐个枚举状态。
  4. 参数获取笔误:在OnNavigatingTo里,你写的是parameters.GetValue<string>("Object1"),但参数Key应该是"Object1Id"吧?这会导致你拿不到正确的ID,异步获取数据肯定会出问题。
  5. 不能删除的“空分支”:你觉得那个“永远不会到达的分支”其实有可能触发——比如页面还没执行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");
        }
    }
}

优化细节说明

  1. 替换async void为async Task:Prism的DelegateCommand支持异步委托,这样既符合异步编程规范,又避免了async void的异常风险。
  2. 简化任务处理:去掉繁琐的Task.Status判断,直接await任务——不管任务是已完成、正在运行还是失败,都能优雅处理。
  3. 自动触发刷新:在Object1和Object2的SetProperty回调里添加OnObjectsUpdated方法,只要两个对象都就绪,就自动刷新ListView,不用等用户手动触发刷新。
  4. 修复参数笔误:把"Object1"改成"Object1Id",确保能正确获取导航参数里的ID。
  5. 线程安全的任务初始化:用??=运算符确保_loadObjectsTask只会被初始化一次,避免重复发起请求。
  6. 可靠的状态重置:用finally块保证IsRefreshing一定会被重置,不会出现UI一直卡在刷新状态的情况。

额外小建议

  • 试试IAsyncCommand:如果你的Prism版本是7.2及以上,可以用IAsyncCommand替代DelegateCommand,它内置了对加载状态的管理,甚至能省去手动维护IsRefreshing的代码。
  • 添加加载UI:在视图里加个加载指示器(比如ActivityIndicator),绑定IsRefreshing属性,让用户知道数据正在加载,体验更好。
  • 增强错误提示:可以加个ErrorMessage属性,当加载失败时显示错误信息,再给用户个重试按钮,提升容错性。

内容的提问来源于stack exchange,提问作者Amr Al Sayed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:32:11