1

我正在制作我的第一个 Windows 8 客户端。这也是我第一次使用异步方法来加载属性,所以如果这是一个菜鸟问题,请原谅我。

我有一个 WCF 服务,并从 Visual Studio 中客户端的拆分页面模板开始(但我几乎要替换所有内容)。

当我将数据直接放入“SplitPage”代码中的视图时,所有内容都已正确加载并显示出来。但是,当我尝试使用 MVVM 时,该属性在绑定时没有任何项目。由于WCF,它是获取数据的异步调用是问题的原因吗?是否可以在返回属性之前确保它们的数据返回(属性本身似乎无法标记为异步)?

这是我所做的:

出于测试目的,我放置了一个 ListView(代码显示为 listbox,但我在 XAML 中使用了 listview)并将以下代码添加为在 OnNavigatedTo 处理程序中调用的异步方法:

private async void GetUsersList()
{
   ServiceClient client = new ServiceClient();
   List<UserDTO> _users = (await client.GetUsersAsync()).ToList();
   foreach(UserDTO user in _users)
   {
      UserListBox.Items.Add(new UserView(user));
   }
   TestStack.Children.Add(new UsersView());
}

这工作正常,当页面加载时 UserListBox 包含 UserViews。

然后,我尝试进入一个完整的 MVVM 模式,并制作了一个 UsersViewModel 和 UsersView(复数)以及一个存储库,该存储库在其构造函数中使用从我的 WCF 服务中提取的 UserDTO 的 ObservableCollection 初始化属性 Users。这就是上述消息的最后一行所做的就是将视图设置为页面上的堆栈面板。

视图和视图模型在资源文件中粘合在一起:

<DataTemplate x:Key="vm:UsersViewModel">
    <vw:UsersView />
</DataTemplate>

绑定与我习惯的有点不同,因为显然 .Net 4.5 不再具有 DataTemplates 上的 x:Type 属性。

加载数据的存储库部分如下所示:

    private ObservableCollection<UserDTO> _users = new ObservableCollection<UserDTO>();
    private ServiceClient _client = new ServiceClient();

    public UserRepository()
    {
        GetUsers();
    }

    public async void GetUsers()
    {
        var tempList = await _client.GetUsersAsync();
        foreach(UserDTO item in tempList)
        {
            _users.Add(item);
        }
    }

UsersViewModel 的构造函数唯一要做的就是创建存储库的实例并将 UserViewModel 项加载到其可观察的 UserViewModel 集合中:

public UsersViewModel()
{
   _repo = new UserRepository();
   foreach (UserDTO item in _repo.Users)
   {
      _users.Add(new UserViewModel(item.Id));
   }
}

我已经尝试将输出语句放在任何地方,并且确实“getter”属性返回了一个空列表,即使直接在 SplitPage 代码中的相同代码返回了我已经在 WCF 提供的数据库中的测试项目。它可以像代码运行的线程一样简单吗?也许 SplitPage 正在 UI 线程上运行对 WCF 的调用,因此在数据异步调用返回之前不会发生绑定,但是由于某种原因使用 MVVM,数据绑定会在后台线程上加载数据时立即发生?如果是这样,当数据最终确实出现在属性中时,它是 ObservableCollection 的事实不应该通知 UI 吗?

4

1 回答 1

2

async方法在完成执行之前返回。您没有看到任何用户,因为GetUsers返回到UserRepository构造函数,该UsersViewModel构造函数在加载用户之前返回构造函数。

我最喜欢的解决方案是异步工厂方法,例如UserRepository

private UserRepository()
{
}

private async Task InitializeAsync()
{
    var tempList = await _client.GetUsersAsync();
    foreach(UserDTO item in tempList)
    {
        _users.Add(item);
    }
}

public static async Task<UserRepository> Create()
{
    var ret = new UserRepository();
    await ret.InitializeAsync();
    return ret;
}

工厂方法方法的最大好处async是您永远不会获得尚未初始化的实例。

但是,在某些情况下,您必须使用公共构造函数而不是async工厂方法,例如 IoC/DI 或数据绑定。

在这种情况下,我发现以下模式很有帮助:

public MyConstructor()
{
  Initialized = InitializeAsync();
}

public Task Initialized { get; private set; }
private async Task InitializeAsync()
{
  // asynchronous initialization here
}

您可以像这样将它应用到您的存储库:

private ObservableCollection<UserDTO> _users = new ObservableCollection<UserDTO>();
private ServiceClient _client = new ServiceClient();

public UserRepository()
{
    Initialized = InitializeAsync();
}

public Task Initialized { get; private set; }
private async Task InitializeAsync()
{
    var tempList = await _client.GetUsersAsync();
    foreach(UserDTO item in tempList)
    {
        _users.Add(item);
    }
}

然后,您可以在依赖类中使用相同的模式:

public UsersViewModel()
{
    _repo = new UserRepository();
    Initialized = InitializeAsync();
}

public Task Initialized { get; private set; }
private async Task InitializeAsync()
{
    // Wait for the repository to initialize
    await _repo.Initialized;

    foreach (UserDTO item in _repo.Users)
    {
        _users.Add(new UserViewModel(item.Id));
    }
}

附带说明:作为一般规则,您应该避免async void. 您可能会发现我的async/await介绍很有帮助。

于 2013-01-07T04:31:20.107 回答