在ViewModel中异步加载数据(使用async和await),不能使用数据绑定

本文关键字:await 不能 数据绑定 async 异步 ViewModel 加载 数据 使用 | 更新日期: 2023-09-27 18:02:57

我用默认模板启动了一个手机应用程序,该模板已经定义了一个视图模型。我修改了MainViewModel的LoadData()方法,以异步调用odata服务。但它不能与数据绑定一起工作。我已经验证调用返回成功,但没有显示结果。

LongListSelector的items源被绑定到视图模型中的items属性。

<phone:LongListSelector ItemsSource="{Binding Items}" x:Name="MainLongListSelector" Margin="0,0,-12,0" SelectionChanged="MainLongListSelector_SelectionChanged">
                <phone:LongListSelector.ItemTemplate>
                    <DataTemplate>
                      <StackPanel Margin="0,0,0,17">
                            <TextBlock Text="{Binding UnReadCount}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                            <TextBlock Text="{Binding description}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                      </StackPanel>
                    </DataTemplate>
                </phone:LongListSelector.ItemTemplate>
            </phone:LongListSelector>

这是我对视图模型的修改(注意async和await的使用):

public void LoadData()
    {
        FetchTileViewItems();        
    }
    private async void FetchTileViewItems()
    {
        var ret = await I2ADataServiceHelper.GetTileViewItemsAsync();
        this.Items = new ObservableCollection<TileViewItem>(ret);
        this.IsDataLoaded = true;
    }

我在页面上的NavigatedTo事件中调用LoadData()方法,就像之前一样:

protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (!App.ViewModel.IsDataLoaded)
            {
                App.ViewModel.LoadData();
                pr1.IsVisible = false;
            }
        }

点击运行,没有任何显示…我错过什么了吗?

在ViewModel中异步加载数据(使用async和await),不能使用数据绑定

好的,快速的答案是你可能在Items和/或IsDataLoaded设置上缺少INotifyPropertyChanged通知。

更长的答案需要一点时间。:)

首先,您应该避免async void。我在我的文章"异步编程的最佳实践"中详细描述了原因。在这种情况下,请考虑您的错误处理。当"调用成功返回"时,您的快乐情况是好的,但悲伤情况将使您的程序崩溃。

那么,让我们尽可能地将所有内容重写为async Task,并遵循*Async约定:

public async Task LoadDataAsync()
{
    await FetchTileViewItemsAsync();
}
private async Task FetchTileViewItemsAsync()
{
    var ret = await I2ADataServiceHelper.GetTileViewItemsAsync();
    this.Items = new ObservableCollection<TileViewItem>(ret);
    this.IsDataLoaded = true;
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    if (!App.ViewModel.IsDataLoaded)
    {
        await App.ViewModel.LoadDataAsync();
    }
}

这是编写async代码更自然的方式。

接下来,让我们修复这个错误情况。您可以OnNavigatedTo中执行try/catch:

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    try
    {
        if (!App.ViewModel.IsDataLoaded)
        {
            await App.ViewModel.LoadDataAsync();
        }
    }
    catch (Exception ex)
    {
        ...
    }
}

但我实际上更倾向于一个以viewmodel为中心的、数据绑定友好的错误处理系统。这样,"断开连接"对于应用程序来说是一个非常自然的状态;即使它所做的只是显示一条错误消息,您的应用程序最终也是为偶尔连接的系统(例如电话)而设计的。而且,生成的代码更易于测试。

我在我的几篇博客文章中描述了这种方法:我在async构造函数的文章中介绍了异步初始化模式,在async属性的文章中特别介绍了数据绑定。我编写了一个名为TaskCompletionNotifier的辅助类,它使您能够使用Task与数据绑定。

把这些设计放在适当的地方,你的ViewModel代码最终看起来更像这样:

public sealed class MyViewModel : INotifyPropertyChanged
{
    public ObservableCollection<TileViewItem> Items
    {
      get { return _items; }
      private set { _items = value; RaisePropertyChanged(); }
    }
    public ITaskCompletionNotifier Initialization { get; private set; }
    public MyViewModel()
    {
        Initialization = TaskCompletionNotifierFactory.Create(InitializeAsync());
    }
    private async Task InitializeAsync()
    {
        var ret = await I2ADataServiceHelper.GetTileViewItemsAsync();
        this.Items = new ObservableCollection<TileViewItem>(ret);
    }
}

(这是假设你想在构造函数中开始加载数据。)

你可以直接绑定到Items,你也可以绑定到Initialization.IsSuccessfullyCompleted为快乐的情况,Initialization.IsFaultedInitialization.ErrorMessage为悲伤的情况,等等