MVVM c#如何将异步数据加载到属性中?

8

我想知道在将异步数据加载到属性中是否有更好的方法。现在我创建了一个异步函数,并在Get部分的属性中引发了一个任务,就像这样:

private ObservableCollection<CProyecto> prope;

public ObservableCollection<CProyecto> Prope
{
    get 
    {
        if (prope == null)
        {
            Task.Run(()=> LoadData()).Wait();
        }

        return proyectos;
    }
    set 
    { 
        prope = value; 
        RaisePropertyChanged(); 
    }
}

async private Task LoadData() 
{
    Prope = await clsStaticClassDataLoader.GetDataFromWebService();
}

这种方法是可行的,但我不喜欢使用.Wait,因为如果服务响应不够快,它会冻结屏幕。您能否在这个问题上给我指导?
提前感谢。

1
如果您调用Wait()方法阻塞调用线程,那么启动新任务的意义何在呢? - mm8
关于异步属性,您应该阅读此文:https://blog.stephencleary.com/2013/01/async-oop-3-properties.html - mm8
使用“loaded event”从外部源获取数据。将事件设置为异步并绑定到您的集合。 - Eldho
1
史上最糟糕的属性。 - user1228
Eldho,从XAML页面加载的loaded_event?谢谢。 - KillemAll
3个回答

12

我处理这个问题的方法是在对象构造时开始加载属性,但我没有等待结果。由于属性在填充时发出通知,因此绑定正常工作。基本上它的工作原理如下:

public class MyClass : INotifyPropertyChanged
{
    private ObservableCollection<CProyecto> prope;

    public ObservableCollection<CProyecto> Prope
    {
        get { return prope; }
        set { prope = value; RaisePropertyChanged(nameof(Prope)); }
    }

    public MyClass()
    {
        // Don't wait or await.  When it's ready
        // the UI will get notified.
        LoadData();
    }

    async private Task LoadData() 
    {
        Prope = await clsStaticClassDataLoader.GetDataFromWebService();
    }
}

这很好地运作,不会导致UI延迟或卡顿。如果你想让集合永远不为null(我认为这是一个好习惯),你可以使用空集合预初始化prope字段。


4
这是一个不错的第一步,但它会默默地吞没“LoadData”抛出的错误。一个更全面的方法是在“LoadData”中使用顶层的“try”/“catch”,并使用错误通知更新您的UI界面。 - Stephen Cleary
2
同意。我正在描述我所采取的方法。但是,你已经找到了正确的位置来处理错误。 - Berin Loritsch
@KillemAll,你会收到警告,因为你无法await响应。 这里的目标不是等待,而是启动加载数据的过程。 我们以另一种方式被通知工作已完成。 - Berin Loritsch
我按照你说的那样去做了,除了使用 LoadData().wait();之外,我没有收到任何警告。还有什么我漏掉的吗? - KillemAll
只需忽略警告即可。这不是编译失败。由于它是“有意的”,您可以使用[SuppressMessage]属性来抑制警告。 - Berin Loritsch
显示剩余3条评论

12

我建议你阅读我的 MSDN 文章 关于异步 MVVM 数据绑定。我有一个提供了 (GitHub 链接),它提供了一个 NotifyTask<T> 类型,可以这样使用:

public class MyClass : INotifyPropertyChanged
{
  public NotifyTask<ObservableCollection<CProyecto>> Prope { get; private set; }

  public MyClass()
  {
    // Synchronously *start* the operation.
    Prope = NotifyTask.Create(LoadDataAsync());
  }

  async private Task<ObservableCollection<CProyecto>> LoadDataAsync()
  {
    return await clsStaticClassDataLoader.GetDataFromWebService();
  }
}

那么您的数据绑定将操作于Prope.Result

这种方法的优点在于,您还可以使用数据绑定来隐藏/显示繁忙指示器(Prope.IsNotCompleted),在数据可用时显示控件(Prope.IsSuccessfullyCompleted),以及错误通知(Prope.IsFaulted/Prope.ErrorMessage)。

此外,如果您愿意,还可以指定非null的默认值:

Prope = NotifyTask.Create(LoadDataAsync(), new ObservableCollection<CProyecto>());

现在可以在 GitHub 上找到该库: https://github.com/StephenCleary/Mvvm.Async - cmxl
好的,问题有点傻,我该如何将这个库加载到我的项目中?我正在使用旧版本的INotifyTaskCompletion和NotifyTaskCompletion.Create()。我在Stephen Cleary的旧帖子中找到了它,并且它在Nito.AsyncEx.Strong的Nuget包中。 - GarudaLead
@FLAdmin:抱歉造成困惑;我认为异步MVVM类型经历了比我编写的任何其他代码都更多的包重命名。它们目前位于:https://www.nuget.org/packages/Nito.Mvvm.Async - Stephen Cleary
非常感谢Stephen。我会导入那个包。实际上,我从你的文章中复制了NotifyTaskCompletion类,它运行得非常好,让我继续前进。https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/march/async-programming-patterns-for-asynchronous-mvvm-applications-data-binding - GarudaLead

0

您当前的Prope属性实现没有什么意义。在后台线程上执行LoadData方法是毫无意义的,因为当您调用Wait()时,主线程会被阻塞。 您可以直接调用由LoadData()方法返回的任务上的Wait()方法:

//BAD IMPLEMENTATION!
private ObservableCollection<CProyecto> prope;
public ObservableCollection<CProyecto> Prope
{
    get
    {
        if (prope == null)
            LoadData().Wait();
        return proyectos;
    }
    set { prope = value; RaisePropertyChanged(); }
}

上述实现仍然不好。属性的getter不应该执行异步操作。您应该阅读@Stephen Cleary关于此主题的博客文章:https://blog.stephencleary.com/2013/01/async-oop-3-properties.html

...并查看他在他的AsyncEx库中的NotifyTaskCompletion类型:https://github.com/StephenCleary/AsyncEx


看起来好像没有意义,但实际上是有意义的。如果我不这样做,在调用我的 Web 服务时系统会挂起。我猜这是因为我调用服务的方式不对,变量响应 = client.GetAsync(url).Result; - KillemAll

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接