【问题标题】:WinRT ViewModel DataBind to async methodWinRT ViewModel DataBind 到异步方法
【发布时间】:2012-08-04 15:49:54
【问题描述】:

我正在反序列化 XML 文件中的对象列表,并希望通过 ViewModel 绑定到我的视图中这些对象的实际内容。问题是文件操作是async,并且一直冒泡到 ViewModel,其中属性 getter 不能被标记为...

问题

  • 我将文件夹中的所有 XML 文件反序列化为 Profile 对象并将它们存储在 List<Profile> 中。此方法(必须)标记为async

        public static async Task<List<Profile>> GetAllProfiles()
        {
            DataContractSerializer ser = new DataContractSerializer(typeof(Profile));
            StorageFolder folder = await ApplicationData.Current.RoamingFolder.CreateFolderAsync("Profiles", CreationCollisionOption.OpenIfExists);
    
            List<Profile> profiles = new List<Profile>();
            foreach (var f in await folder.GetFilesAsync())
            {
                var fs = await f.OpenStreamForReadAsync();
                profiles.Add((Profile)ser.ReadObject(fs));
               fs.Dispose();
            }
    
            return profiles;
        }
    

理想方案一

  • 我的 ViewModel 中的绑定属性会理想地像这样调用静态方法

    public async Task<ObservableCollection<string>> Lists
    {
        get 
        {
            return new ObservableCollection<string>(GetAllProfiles().Select(p => p.Name));
        }
    }
    
  • BUT 属性无法标记async

理想方案2

    public ObservableCollection<string> Lists
    {
        get 
        {
            return new ObservableCollection<string>((GetAllProfiles().Result).Select(p => p.Name));
        }
    }
  • 但是这永远不会执行(由于某种原因它在 await folder.GetFilesAsync() 调用中阻塞)

当前解决方案

调用asyncInitialize() 方法,将GetProfiles() 函数的结果加载到变量中,然后调用NotifyPropertyChanged("Lists")

    public ViewModel()
    {
        Initialize();
    }

    public async void Initialize()
    {
        _profiles = await Profile.GetAllProfiles();
        NotifyPropertyChanged("Lists");
    }

    private List<Profile> _profiles;
    public ObservableCollection<string> Lists
    {
        get
        {
            if (_profiles != null)
                return new ObservableCollection<string>(_profiles.Select(p => p.Name));
            else
                return null;
        }
    }

问题

有没有更好的方法? 有没有我还没发现的模式/方法?

编辑

问题的根源出现在做非 UI 代码时,你不能依赖 NotifyPropertyChanged 来做一些线程同步的事情。 -- Initialize 方法必须等待,并且ctors 不能是异步的,所以本质上这是模式是无用的。

    public MyClass()
    {
        Initialize();
    }

    public async void Initialize()
    {
        _profiles = await Profile.GetAllProfiles();
    }

    private ObservableCollection<Profile> _profiles;
    public ObservableCollection<string> Lists
    {
        get
        {
            return _profiles; // this will always be null
        }
    }

【问题讨论】:

  • 您应该在设置 _profiles 值时引发 PropertyChanged 事件。此外 - _profiles 和 Lists 是不同的类型 - 它们需要是相同的类型。
  • 同意(这只是复制/粘贴伪代码),但这只是普通的 C#,因此没有可用的 UI 代码和 PropertyChanged 事件。我想要的是从异步方法中获取结果,而不必将我的所有方法都标记为异步(因为当您点击 UI 时,它会停止),并且 .Result() 调用死锁,正如我们在下面发现的......
  • 那么,您需要在视图模型中使用它,实现 INotifyPropertyChanged 并引发事件。
  • 假设它的代码用于与 UI 无关的业务类 :)
  • 那么它将位于 MVVM 世界的模型端,并且视图模型需要等待模型更新,然后再提升其 PropertyChanged。否则,如果您的视图绑定到您的业务逻辑模型 - 应该没有什么可以阻止您在业务逻辑模型中实现 INotifyPropertyChanged。如果你不能这样做 - 你需要等待配置文件列表加载。

标签: data-binding mvvm asynchronous viewmodel windows-runtime


【解决方案1】:

属性不能是异步的,所以这个解决方案不会像你提到的那样工作。 Task.Result 等待任务完成,但这会阻塞 I/O 操作的异步回调返回的 UI 线程,因此您将死锁您的应用程序,因为从不调用回调。您的解决方案确实是最好的方法。不过可以改进。

  • 您应该将 _profiles 字段设为 ObservableCollection,这样您就不需要在每次访问列表时都将列表转换为 OC。
  • 由于您正在执行可能需要任意时间的 I/O 操作 - 您应该在它进行时启用某种进度指示器。
  • 在某些情况下,您可能希望 Lists 属性更加惰性,并且仅在首次访问时调用 Init 方法。

【讨论】:

  • 感谢您的回答和提示。我实现了它们,确实更好!遗憾的是没有更清洁的方法来做到这一点......任何线索为什么理想的解决方案 2 块(并且永远不会恢复)?
  • Result 属性不是异步的(正如我们所确定的),而是同步调用,因此线程的执行会暂停,直到 Result 返回,这发生在 Task 完成时。我的理解是异步 I/O 操作在与某种事件相同的线程上返回,但是该线程的调度程序没有运行,因为线程暂停等待 Result 属性返回,因此无法处理任何事件并且你最终得到一个deadlock
猜你喜欢
  • 1970-01-01
  • 2011-11-07
  • 2017-11-11
  • 2015-11-02
  • 2017-10-18
  • 1970-01-01
  • 2017-02-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多