C# 在 ViewModel 中异步加载数据(使用 async 和 await)不适用于数据绑定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15305189/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 16:23:09  来源:igfitidea点击:

Loading data in ViewModel asynchronously (with async and await) not working with databinding

c#data-bindingmvvmwindows-phone-8async-await

提问by user2137225

I started a phone app with the default template which has a view model already defined. I modified the MainViewModel's LoadData() method to call an odata service asynchronously. But it is not working with the databinding. I have verified that the call returned successfully but no result is displayed.

我使用默认模板启动了一个电话应用程序,该模板已经定义了一个视图模型。我修改了 MainViewModel 的 LoadData() 方法以异步调用 odata 服务。但它不适用于数据绑定。我已验证调用成功返回,但未显示任何结果。

The LongListSelector's items source is bound to the Items property in the view model.

LongListSelector 的项目源绑定到视图模型中的 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>

Here's my modification to the view model (note the async and await usage):

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

public void LoadData()
    {
        FetchTileViewItems();        
    }

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

And I'm calling the LoadData() method in the NavigatedTo event on the page just like before:

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

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

Hit run and nothing shows up...Am I missing anything? Any pointers are greatly appreciated.

点击运行,什么也没有出现......我错过了什么吗?任何指针都非常感谢。

回答by Stephen Cleary

OK, the quick answer is that you're probably missing INotifyPropertyChangednotifications on your Itemsand/or IsDataLoadedsetters.

好的,快速回答是您可能错过了INotifyPropertyChanged关于您Items和/或IsDataLoadedsetter 的通知。

The longer answer will take a bit. :)

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

First, you should avoid async void. I describe why in detail in my Best Practices in Asynchronous Programmingarticle. In this case, consider your error handling. It's nice that your happy case is when "the call returned successfully" but the sad case will tear your program up.

首先,你应该避免async void. 我在我的异步编程最佳实践文章中详细描述了原因。在这种情况下,请考虑您的错误处理。很高兴你的快乐案例是“调用成功返回”,但悲伤的案例会撕裂你的程序。

So, let's rewrite everything as async Taskas much as possible, and follow the *Asyncconventionwhile we're at it:

因此,让我们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();
    }
}

This is the more natural way to write asynccode.

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

Next, let's fix up that error situation. You cando a try/catchin OnNavigatedTo:

接下来,让我们修复那个错误情况。你可以做一个try/ catchin OnNavigatedTo

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

But I actually lean more towards a ViewModel-centric, databinding-friendly system for error handling. That way, "disconnected" is a perfectly natural state for your application; even if all it does is display an error message, your application ends up being designedfor a occasionally-connected system (i.e., a phone). Also, the resulting code is more testable.

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

I describe this approach in a couple of my blog posts: I cover the asynchronous initialization patternin my post on asyncconstructors, and the data-binding in particularin my post on asyncproperties. I wrote a helper class called TaskCompletionNotifierwhich enables you to use Taskwith data binding.

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

Putting these designs in place, your ViewModel code ends up looking more like this:

将这些设计放在适当的位置,您的 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);
    }
}

(This is assuming you want to start loading data in the constructor.)

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

You can then bind to Itemsdirectly, and you can also bind to Initialization.IsSuccessfullyCompletedfor the happy case, Initialization.IsFaultedand Initialization.ErrorMessagefor the sad case, etc.

然后,您可以绑定到Items直接,你也可以绑定到Initialization.IsSuccessfullyCompleted对幸福的情况下,Initialization.IsFaultedInitialization.ErrorMessage为悲伤的情况,等等。