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

如何异步实现预填充ObservableCollection后ItemsSource正常绑定显示

问题:异步过滤数据后导航并绑定CarouselView的异常问题

我有一个CarouselView,它的ItemsSource绑定了一个ObservableCollection。目前在OnAppearing事件里执行ViewModel的命令时,集合能正常绑定显示。现在想实现:点击第一页的按钮后,先异步过滤数据并填充ObservableCollection,完成后再导航到第二页,但尝试这个方案时出现空引用异常,而且ItemsSource不显示数据。之前试过把ObservableCollection作为参数传到第二页,但复制集合会导致界面卡顿,请问该怎么正确实现这个异步处理逻辑?


可用代码(当前能运行但不符合需求的实现)

第二页代码

public partial class SecondPage : ContentPage
{
    // 构造函数接收筛选参数
    public SecondPage(bool hard, string subject)
    {
        InitializeComponent();
        var vm = (DataSelectionViewModel)BindingContext;

        vm.Hard = hard;
        vm.Subject = subject;
        /* 必须在这里重新设置hard和subject,否则数据加载不出来 */
    }

    protected override async void OnAppearing()
    {
        var vm = (DataSelectionViewModel)BindingContext;
        base.OnAppearing();
        // 在页面显示时执行数据加载命令
        await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
    }
}

第二页ViewModel

public class DataSelectionViewModel : BaseViewModel
{
    private string _subject;
    public string Subject 
    { 
        get => _subject; 
        set => SetProperty(ref _subject, value); 
    }

    private bool _hard;
    public bool Hard 
    { 
        get => _hard; 
        set => SetProperty(ref _hard, value); 
    }

    public ObservableCollection<Items> FilteredData { get; set; }

    private readonly IDataStore _dataStore;
    public AsyncCommand LoadData { get; }

    public DataSelectionViewModel()
    {
        _dataStore = DependencyService.Get<IDataStore>();
        LoadData = new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
        // 初始化集合
        FilteredData = new ObservableCollection<Items>();
    }

    // 异步筛选数据
    public async Task FilterData()
    {
        FilteredData.Clear();
        var filteredData = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
        foreach (var data in filteredData)
        { 
            FilteredData.Add(data);
        }
    }  
}

第一页按钮点击事件

private async void ButtonClick(object sender, EventArgs e)
{
    var vm = (BaseViewModel)BindingContext;

    vm.Hard = HardButtonSelected == Hard;
    vm.Subject = vm.Subject.ToLower();

    // 直接导航到第二页,数据在第二页的OnAppearing中加载
    await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}

不可用代码(尝试实现需求但出错的版本)

第二页代码

public partial class SecondPage : ContentPage
{
    public SecondPage()
    {
        InitializeComponent();
    }
}

第二页ViewModel

public class DataSelectionViewModel : BaseViewModel
{
    public ObservableCollection<Items> FilteredData { get; set; }

    public DataSelectionViewModel()
    {
        FilteredData = new ObservableCollection<Items>();
    }
}

BaseViewModel

public class BaseViewModel : INotifyPropertyChanged
{
    private string _subject;
    public string Subject 
    { 
        get => _subject; 
        set => SetProperty(ref _subject, value); 
    }

    private bool _hard;
    public bool Hard 
    { 
        get => _hard; 
        set => SetProperty(ref _hard, value); 
    }

    public ObservableCollection<Items> FilteredData { get; set; }

    /* BaseViewModel中实现了SetProperty方法 */
}

第一页按钮点击事件

private async void ButtonClick(object sender, EventArgs e)
{
    var vm = (BaseViewModel)BindingContext;

    vm.Hard = HardButtonSelected == Hard;
    vm.Subject = vm.Subject.ToLower();
}

第一页ViewModel

public class FirstPageViewModel : BaseViewModel
{
    public IAsyncCommand ButtonClickedCommand { get; }
    readonly IPageService _pageService;
    readonly IDataStore _dataStore;

    public FirstPageViewModel()
    {
        _dataStore = DependencyService.Get<IDataStore>();
        ButtonClickedCommand = new AsyncCommand(ButtonClicked);
        _pageService = DependencyService.Get<IPageService>();
    }

    private async Task ButtonClicked()
    {
        // 先筛选数据
        await FilterData();
        // 再导航到第二页
        await _pageService.PushAsync(new SecondPage());
    }

    private async Task FilterData()
    {
        FilteredData.Clear();
        var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
        foreach (var data in datas)
        {
            FilteredData.Add(data);
        }
    }
}

解决方法

1. 修复空引用异常

空异常的核心原因是FirstPageViewModel中的FilteredData未初始化就调用了Clear(),在构造函数中补充初始化即可:

public FirstPageViewModel()
{
    _dataStore = DependencyService.Get<IDataStore>();
    ButtonClickedCommand = new AsyncCommand(ButtonClicked);
    _pageService = DependencyService.Get<IPageService>();
    // 初始化集合,避免空引用
    FilteredData = new ObservableCollection<Items>();
}

2. 实现“筛选完成后再导航”的正确逻辑

方案A:共享ViewModel实例(推荐)

让第二页直接复用第一页的ViewModel,完全避免集合复制操作:

  • 修改第二页构造函数,接收ViewModel实例:
public partial class SecondPage : ContentPage
{
    public SecondPage(BaseViewModel vm)
    {
        InitializeComponent();
        // 直接绑定第一页的ViewModel
        BindingContext = vm;
    }
}
  • 修改第一页ViewModel的导航逻辑:
private async Task ButtonClicked()
{
    await FilterData();
    // 传递当前ViewModel给第二页
    await _pageService.PushAsync(new SecondPage(this));
}

方案B:批量传递数据优化性能

如果必须使用独立ViewModel,不要复制集合,而是传递筛选后的原始列表并批量添加,减少UI更新次数:

  • 给第二页ViewModel添加批量加载数据的方法:
public class DataSelectionViewModel : BaseViewModel
{
    public ObservableCollection<Items> FilteredData { get; set; }

    public DataSelectionViewModel()
    {
        FilteredData = new ObservableCollection<Items>();
    }

    // 批量添加数据,减少UI线程更新次数
    public void LoadFilteredData(List<Items> data)
    {
        FilteredData.Clear();
        foreach(var item in data)
        {
            FilteredData.Add(item);
        }
    }
}
  • 修改第一页导航逻辑:
private async Task ButtonClicked()
{
    var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
    var secondVm = new DataSelectionViewModel();
    secondVm.LoadFilteredData(datas.ToList());
    await _pageService.PushAsync(new SecondPage { BindingContext = secondVm });
}

3. 避免UI线程阻塞

ObservableCollection的操作必须在UI线程执行,若筛选逻辑使用了ConfigureAwait(false),需切换回UI线程更新集合:

private async Task FilterData()
{
    FilteredData.Clear();
    var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
    // 切换回UI线程更新集合
    MainThread.BeginInvokeOnMainThread(() =>
    {
        foreach (var data in datas)
        {
            FilteredData.Add(data);
        }
    });
}

内容的提问来源于stack exchange,提问作者ViDanMaster

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 22:35:17