2013-01-02 92 views
2

我正在构建Windows 8应用程序,并遇到异步调用问题。我会尽量提供尽可能多的细节,可能是因为我觉得我2分的结果,以这样的:窗口8应用程序:嵌套的异步调用

  • 我做的东西完全错误的,当它涉及到异步调用
  • 还是我做错了而且,这也可能是我错误的架构,使我不能摆在首位的问题上。

我是一个牛逼到Windows Azure和MVVM但这里的情况...

该应用现已内置于Windows 8,但我也希望能够使用其他平台,所以我做了什么首先是创建一个发布到Windows Azure网站的WebAPI项目。这样,我可以使用JSON传输数据,并且WebAPI控制器连接到正在处理和来自Window Azure表存储的数据请求的存储库。第二部分是从Azure网站请求数据的MVVM Light Windows 8应用程序。

那么让我们来更详细地看一下WebAPI项目。在这里,我有一个类别模型开始。

public class Category : TableServiceEntity 
{ 
    [Required] 
    public string Name { get; set; } 

    public string Description { get; set; } 

    public string Parent { get; set; } 
} 

类型模型只包含一个名称和描述(id是 TableServiceEntity的RowKey)。在类别嵌套的情况下,还会向父类别添加字符串引用。第一个问题出现了:父类应该是类型类别而不是字符串,并且后端侧的类别模型是否应包含子类别的集合?

然后我有我的IRepository接口来定义存储库。 (正在进行中;-))它也使用规范模式来传递查询范围。这是所有的工作,你可以测试使用浏览器,浏览到:http://homebudgettracker.azurewebsites.net/api/categories

public interface IRepository<T> where T : TableServiceEntity 
{ 
    void Add(T item); 
    void Delete(T item); 
    void Update(T item); 
    IEnumerable<T> Find(params Specification<T>[] specifications); 
    IEnumerable<T> RetrieveAll(); 
    void SaveChanges(); 
} 

现在,仓库是明确的,让我们来看看控制器。我有一个CategoriesController,它只是一个包含IRepository存储库的ApiController。 (与Ninject但这里无关紧要注入)

public class CategoriesController : ApiController 
{ 
    static IRepository<Category> _repository; 

    public CategoriesController(IRepository<Category> repository) 
    { 
     if (repository == null) 
     { 
      throw new ArgumentNullException("repository"); 
     } 

     _repository = repository;  
     } 

控制器包含了一些方法,如,例如:

public Category GetCategoryById(string id) 
{  
    IEnumerable<Category> categoryResults =_repository.Find(new ByRowKeySpecification(id)); 

    if(categoryResults == null) 
    { 
     throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); 
    } 

    if (categoryResults.First<Category>() == null) 
    { 
     throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); 
    } 

    return categoryResults.First<Category>(); 
} 

到现在为止,我们已经看到了后端,让我们转移到这里的实际问题: MvvmLight客户端和对WebAPI控制器的异步http请求。

在客户端项目中,我也有一个类别模型。

public class Category 
{ 
    [JsonProperty("PartitionKey")] 
    public string PartitionKey { get; set; } 

    [JsonProperty("RowKey")] 
    public string RowKey { get; set; } 

    [JsonProperty("Name")] 
    public string Name { get; set; } 

    [JsonProperty("Description")] 
    public string Description { get; set; } 

    [JsonProperty("Timestamp")] 
    public string Timestamp { get; set; } 

    [JsonProperty("Parent")] 
    public string ParentRowKey { get; set; } 

    public ObservableCollection<Category> Children { get; set; } 
} 

不介意PartitionKey和RowKey属性,因为它不涉及Azure的餐桌服务entitiy性能存在哪些应用程序分区键应该被排除在外。 RowKey实际上可以重命名为Id。但在这里没有实际意义。

主要看的视图模型是这样的:

public class MainViewModel : CategoryBasedViewModel 
{ 
    /// <summary> 
    /// Initializes a new instance of the MainViewModel class. 
    /// </summary> 
    public MainViewModel(IBudgetTrackerDataService budgetTrackerDataService) 
: base(budgetTrackerDataService) 
    { 
     PageTitle = "Home budget tracker"; 
    } 
} 

它从我创建共享的逻辑包含一个类别观察的集合页面视图模型延伸。在此视图模型重要的东西:

  • 甲IBudgetTrackerDataService被注入的视图模型,其是高电平数据服务
  • 含有类别 - 视图模型包裹类别的集合
  • 一些性质为一个ObservableCollection绑定(fe:IsLoadingCategories在视图上处理ProgressRing)
  • 由IBudgetTrackerDataService在 之后调用的getCategoriesCompleted回调方法将完成异步调用

因此,代码如下:

public abstract class CategoryBasedViewModel : TitledPageViewModel 
{ 
    private IBudgetTrackerDataService _dataService; 

    private ObservableCollection<CategoryViewModel> _categoryCollection; 

    private Boolean isLoadingCategories; 

    public const string CategoryCollectionPropertyName = "CategoryCollection"; 

    public const string IsLoadingCategoriesPropertyName = "IsLoadingCategories"; 

    public Boolean IsLoadingCategories 
    { 
     get 
     { 
      return isLoadingCategories; 
     } 
     set 
     { 
      if (isLoadingCategories != value) 
      { 
       isLoadingCategories = value; 
       RaisePropertyChanged(IsLoadingCategoriesPropertyName); 
      } 
     } 
    } 

    public ObservableCollection<CategoryViewModel> CategoryCollection 
    { 
     get 
     { 
      return _categoryCollection; 
     } 
     set 
     { 
      _categoryCollection = value; 
      RaisePropertyChanged(CategoryCollectionPropertyName); 
     } 
    } 

    public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService) 
    { 
     wireDataService(budgetTrackerDataService); 
    } 

    public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService, string pageTitle) 
    { 
     PageTitle = pageTitle; 
     wireDataService(budgetTrackerDataService); 
    } 

    private void wireDataService(IBudgetTrackerDataService budgetTrackerDataService) 
    { 
     _dataService = budgetTrackerDataService; 
     CategoryCollection = new ObservableCollection<CategoryViewModel>(); 
     IsLoadingCategories = true; 
     _dataService.GetCategoriesAsync(GetCategoriesCompleted); 
    } 

    private void GetCategoriesCompleted(IList<Category> result, Exception error) 
    { 
     if (error != null) 
     { 
      throw new Exception(error.Message, error); 
     } 

     if (result == null) 
     { 
      throw new Exception("No categories found"); 
     } 

     IsLoadingCategories = false; 

     CategoryCollection.Clear(); 

     foreach (Category category in result) 
     { 
      CategoryCollection.Add(new CategoryViewModel(category, _dataService)); 
      // Added the dataService as a parameter because the CategoryViewModel will handle the search for Parent Category and Children catagories 
     } 
    } 
} 

这是所有工作,但现在我想父/子关系的类别的运作。对于这一点,我已经 添加逻辑来CategoryViewModel使其获取子类构造时...

public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService 
budgetTrackerDataService) 
{ 
    _category = categoryModel; 
    _dataService = budgetTrackerDataService; 

    // Retrieve all the child categories for this category 
    _dataService.GetCategoriesByParentAsync(_category.RowKey, 
GetCategoriesByParentCompleted); 
} 

所以一个CategoryBasedViewModel的建设获取类别和调用回调方法GetCategoriesCompleted:

_dataService.GetCategoriesAsync(GetCategoriesCompleted); 

该回调方法也调用了CategoryViewModel的构造函数。在那里,使用另一个异步方法来获取类别的子项。

public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService 
budgetTrackerDataService) 
{ 
    _category = categoryModel; 
    _dataService = budgetTrackerDataService; 

    // Retrieve all the child categories for this category 
    _dataService.GetCategoriesByParentAsync(_category.RowKey, 
GetCategoriesByParentCompleted); 
} 

还有我的问题! GetCategoriesByParentAsync是另一个异步调用中发生的异步调用,代码刚刚脱离调用并且什么也不做。筑巢时

  • 为什么这些调用失败:

    数据服务实现的接口:

    public interface IBudgetTrackerDataService 
    { 
        void GetCategoriesAsync(Action<IList<Category>, Exception> callback); 
    
        void GetCategoriesByParentAsync(string parent, Action<IList<Category>, 
    Exception> callback); 
    } 
    

    异步方法包含下面的代码:

    public async void GetCategoriesAsync(Action<IList<Category>, Exception> callback) 
    { 
        // Let the HTTP client request the data 
        IEnumerable<Category> categoryEnumerable = await _client.GetAllCategories(); 
    
        // Invoke the callback function passed to this operation 
        callback(categoryEnumerable.ToList<Category>(), null); 
    } 
    
    public async void GetCategoriesByParentAsync(string parent, Action<IList<Category>, 
    Exception> callback) 
    { 
        // Let the HTTP client request the data 
        IEnumerable<Category> categoryEnumerable = await 
    _client.GetCategoriesWithParent(parent); 
    
        // Invoke the callback function passed to this operation 
        callback(categoryEnumerable.ToList<Category>(), null); 
    } 
    

    长话短说来电?

  • 其次,我是愚蠢的,我应该处理父母/子女 关系笼子的不同?
+1

只是一个提示:如果您发布较短的问题,人们可能更有可能帮助您。如果您有不止一个问题,请单独询问。 – svick

回答

4

我现在要回避父母/子女关系问题,只是针对async问题。

首先,对于async代码一对夫妇的一般准则,我详细我async/await intro blog post解释:

  • 避免async void(返回TaskTask<T>代替)。
  • 适用时使用ConfigureAwait(false)

我见过callback委托人采取的方法,但我不确定它来自哪里。它不适用于async,仅用于使代码IMO复杂化。 Task<T>类型旨在表示与Exception结合的结果值,它可与await无缝配合使用。

因此,首先,你的数据服务:

public interface IBudgetTrackerDataService 
{ 
    Task<IList<Category>> GetCategoriesAsync(); 
    Task<IList<Category>> GetCategoriesByParentAsync(string parent); 
} 

public async Task<IList<Category>> GetCategoriesAsync() 
{ 
    // Let the HTTP client request the data 
    IEnumerable<Category> categoryEnumerable = await _client.GetAllCategories().ConfigureAwait(false); 
    return categoryEnumerable.ToList(); 
} 

public async Task<IList<Category>> GetCategoriesByParentAsync(string parent) 
{ 
    // Let the HTTP client request the data 
    IEnumerable<Category> categoryEnumerable = await _client.GetCategoriesWithParent(parent).ConfigureAwait(false); 
    return categoryEnumerable.ToList(); 
} 

或更好,但如果你实际上并不需要IList<T>

public interface IBudgetTrackerDataService 
{ 
    Task<IEnumerable<Category>> GetCategoriesAsync(); 
    Task<IEnumerable<Category>> GetCategoriesByParentAsync(string parent); 
} 

public Task<IEnumerable<Category>> GetCategoriesAsync() 
{ 
    // Let the HTTP client request the data 
    return _client.GetAllCategories(); 
} 

public Task<IEnumerable<Category>> GetCategoriesByParentAsync(string parent) 
{ 
    // Let the HTTP client request the data 
    return _client.GetCategoriesWithParent(parent); 
} 

(在这一点上,你可能会质疑什么目的你数据服务正在服务)。


移动到MVVM async问题:async不玩的构造特别好。我在几周内发布了一篇博文,详细介绍了这一点,但这里的要点是:

我个人的偏好是使用异步工厂方法(例如,public static async Task<MyType> CreateAsync()),但这并非总是可行特别是如果您为虚拟机使用DI/IoC。在这种情况下,我喜欢公开我需要异步初始化的类型的属性(实际上,我使用IAsyncInitialization接口,但对于您的代码,约定也可以):public Task Initialized { get; }

此属性只设置一次在构造函数中,像这样:

public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService budgetTrackerDataService) 
{ 
    _category = categoryModel; 
    _dataService = budgetTrackerDataService; 

    // Retrieve all the child categories for this category 
    Initialized = InitializeAsync(); 
} 

private async Task InitializeAsync() 
{ 
    var categories = await _dataService.GetCategoriesByParentAsync(_category.RowKey); 
    ... 
} 

你就必须让你的“父” VM等待它的“孩子”虚拟机初始化的选项。目前尚不清楚,这是你想要的,但我会假设你想IsLoadingCategoriestrue直到所有的子虚拟机加载了:

public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService) 
{ 
    _dataService = budgetTrackerDataService; 
    CategoryCollection = new ObservableCollection<CategoryViewModel>(); 
    IsLoadingCategories = true; 
    Initialized = InitializeAsync(); 
    NotifyOnInitializationErrorAsync(); 
} 

private async Task InitializeAsync() 
{ 
    var categories = await _dataService.GetCategoriesAsync(); 
    CategoryCollection.Clear(); 
    foreach (var category in categories) 
    { 
    CategoryCollection.Add(new CategoryViewModel(category, _dataService)); 
    } 

    // Wait until all CategoryViewModels have completed initializing. 
    await Task.WhenAll(CategoryCollection.Select(category => category.Initialized)); 

    IsLoadingCategories = false; 
} 

private async Task NotifyOnInitializationErrorAsync() 
{ 
    try 
    { 
    await Initialized; 
    } 
    catch 
    { 
    NotifyPropertyChanged("InitializationError"); 
    throw; 
    } 
} 

public string InitializationError { get { return Initialized.Exception.InnerException.Message; } } 

我加入了InitializationErrorNotifyOnInitializationErrorAsync展示给表面的任何一种方式初始化期间可能发生的错误。由于Task未执行INotifyPropertyChanged,因此如果/初始化失败时没有自动通知,因此您必须明确表示它。

+0

哇斯蒂芬!这比我所希望的更多:-) 巧合的是,我昨天看到了Scott Hanselman关于[Async and await]的视频(http://www.asp.net/vnext/overview/aspnet/async-and-await) 。 但是,您的博客文章以及您的答案告诉我如何在我的解决方案中实现它的事实帮助我获得更多帮助! 我打算实施这个,让你知道它是否有效。 thx! –

+0

我正在尝试代码,但我有几个问题。 首先,在CategoryViewModel的构造函数中,我看到:Initialized = InitializeAsync();但财产只有一个get;所以只读,正确?其次,这行代码:foreach(var category in categories) 不应该是:foreach(var category in categories.result)? –

+0

1)该属性有一个私人setter,我从(公共API)描述中省略。这是一个只读属性,但是,逻辑上(只在构造函数中设置)。 2)我忘了“等待”;看到更新的代码。不要在'async'代码中使用'Task.Result';改用'await'。 –