Xamarin Forms异步任务中Binding不更新问题求助
异步加载ObservableCollection后SfListView不显示数据的问题
同步加载List完全正常,但异步加载时,即使触发OnPropertyChanged且使用ObservableCollection,数据仍无法显示。仅能通过阻塞UI的方式实现(非合理方案),以下是已尝试的方案、环境信息及有效解决方案:
已尝试的方案
- 使用ListView的
Loaded事件,通过EventToCommandBehavior转换为AsyncCommand避免async void,ViewModel代码如下:
public class FilteredPackagesPageViewModel : BaseViewModel { private ObservableCollection<Datum> states; private string unitPriceWithDiscount; public string UnitPriceWithoutDiscount { get => unitPriceWithDiscount?? string.Empty; set { unitPriceWithDiscount = value; OnPropertyChanged(nameof(UnitPriceWithoutDiscount)); } } public ObservableCollection<Datum> States { get { return states; } set { SetProperty(ref states, value); } } public IAsyncCommand<int> FavouriteCommand { get; set; } public TaskLoaderNotifier<ObservableCollection<Datum>> Loader { get; } public IAsyncCommand<int> PackageDetailCommand { get; set; } public AsyncCommand LoadDetailCommand { get; set; } public FilteredPackagesPageViewModel() { FavouriteCommand = new AsyncCommand<int>((arg) => OnLoginClicked(arg)); PackageDetailCommand = new AsyncCommand<int>((id) => PackageDetailCommandAction(id)); LoadDetailCommand = new AsyncCommand(LoadPageDetail); //States = DataService.GetStates(); } private async Task LoadPageDetail() { var test = await DataService.GetStates(); var test1 = new ObservableCollection<Datum>(); foreach(var state in test) { test1.Add(state); } States = test1; OnPropertyChanged(nameof(States)); }
- 在代码后台的
OnAppearing中调用加载方法,无效果; - 尝试初始化异步构造函数并从调用页面调用,无效;
- 仅能通过阻塞UI的方式实现(非合理方案):
public FilteredPackagesPageViewModel() { FavouriteCommand = new AsyncCommand<int>((arg) => OnLoginClicked(arg)); PackageDetailCommand = new AsyncCommand<int>((id) => PackageDetailCommandAction(id)); LoadTourPackages(); } public async void LoadTourPackages() { Task.Run(async () => await LoadToursAsync()).Wait(); } public async Task LoadToursAsync() { States = await DataService.GetStates(); }
- 尝试使用
BeginInvokeOnMainThread(),同样无效。
当前环境
- Visual Studio 2022
- Xamarin Forms 5.0.0
View代码
<Grid BackgroundColor="white"> <Grid RowDefinitions="20,Auto" RowSpacing="15" Padding="8"> <syncfusion:SfListView x:Name="listView" ItemSize="310" Grid.Row="1" ItemSpacing="0,10,0,5" ItemsSource="{Binding States,Mode=OneWay}"> <syncfusion:SfListView.ItemTemplate> <DataTemplate> <ViewCell> <viewcell:PackageOverviewViewCell ImageUrl="{Binding tourImage,Mode=OneWay}" Price="{Binding UnitPriceAfterDiscount,Mode=OneWay}" Day="{Binding day,Mode=OneWay}" TourStartdate="{Binding tourStartdate,Mode=OneWay}" TourEndDate="{Binding tourEnddate,Mode=OneWay}" Night="{Binding night,Mode=OneWay}" Duration="{Binding duration,Mode=OneWay}" tourPackageId="{Binding tourPackageId,Mode=OneWay}" name="{Binding name,Mode=OneWay}" PackageDetailCommand="{Binding Path=BindingContext.PackageDetailCommand, Source={x:Reference filtered},Mode=TwoWay}" PackageDetailCommandParameter="{Binding tourPackageId}" FavoriteCommand="{Binding Path=BindingContext.FavouriteCommand, Source={x:Reference filtered},Mode=TwoWay}"/> </ViewCell> </DataTemplate> </syncfusion:SfListView.ItemTemplate> <syncfusion:SfListView.Behaviors> <extensions:EventToCommandBehavior EventName="Loaded" Command="{Binding LoadDetailCommand}"/> </syncfusion:SfListView.Behaviors> </syncfusion:SfListView> </Grid> </Grid>
注:绑定无问题,同步或不等待ObservableCollection填充时绑定均正常。
解决方案及问题分析
核心问题点
- SfListView加载时机偏差:
Loaded事件可能在控件绑定上下文未完全初始化时触发,导致数据更新未被控件捕获; - 线程调度缺失:异步方法执行后,若未确保在UI线程更新绑定属性,控件无法感知数据变化;
- SetProperty线程安全性:BaseViewModel中的
SetProperty方法可能未处理UI线程调度,导致PropertyChanged事件在后台线程触发,绑定失效。
有效解决方案
方案1:在Page的OnAppearing中异步调用加载方法
放弃Loaded事件触发,改为在Page的OnAppearing中异步调用ViewModel加载方法,确保绑定上下文完全初始化:
Page后台代码:
protected override async void OnAppearing() { base.OnAppearing(); if (BindingContext is FilteredPackagesPageViewModel vm) { await vm.LoadPageDetail(); } }
同时确保ViewModel的加载方法在UI线程更新属性:
private async Task LoadPageDetail() { var test = await DataService.GetStates(); // 直接用构造函数初始化ObservableCollection,简化代码 var test1 = new ObservableCollection<Datum>(test); // 强制在UI线程更新绑定属性 Device.BeginInvokeOnMainThread(() => { States = test1; }); }
方案2:确保BaseViewModel的SetProperty线程安全
检查BaseViewModel中的SetProperty实现,确保它在UI线程触发OnPropertyChanged事件:
protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "") { if (EqualityComparer<T>.Default.Equals(backingStore, value)) return false; backingStore = value; // 强制在UI线程触发属性变更事件 Device.BeginInvokeOnMainThread(() => OnPropertyChanged(propertyName)); return true; }
方案3:强制刷新SfListView
若数据已更新但控件未刷新,可通过MessagingCenter发送刷新指令:
ViewModel中发送刷新消息:
private async Task LoadPageDetail() { var test = await DataService.GetStates(); States = new ObservableCollection<Datum>(test); MessagingCenter.Send(this, "RefreshListView"); }
Page中订阅并执行刷新:
protected override void OnAppearing() { base.OnAppearing(); MessagingCenter.Subscribe<FilteredPackagesPageViewModel>(this, "RefreshListView", (sender) => { listView.RefreshView(); }); } protected override void OnDisappearing() { base.OnDisappearing(); MessagingCenter.Unsubscribe<FilteredPackagesPageViewModel>(this, "RefreshListView"); }
内容的提问来源于stack exchange,提问作者Prashant Rajput
相关产品推荐
相关产品推荐

