c# - Window 8应用程序:嵌套异步调用

标签 c# windows-8 repository mvvm-light async-await

我正在构建Windows 8应用,但遇到异步调用的麻烦。我将尝试提供尽可能多的细节,因为我认为我有2个结果:


关于异步调用,我做的事情完全错误
或我做错了,但也可能是我使用了错误的体系结构,使我不得不首先解决这个问题。


我是Windows Azure和MVVM的新手,但是这种情况…

该应用程序现在是为Windows 8构建的,但我也希望能够使用其他平台,因此我首先要做的是创建一个WebAPI项目,该项目将发布到Windows Azure网站上。这样,我可以使用JSON传输数据,并且WebAPI控制器连接到正在处理往返Windows Azure表存储的数据请求的存储库。第二部分是MVVM Light Windows 8应用程序,该应用程序从Azure网站请求数据。

因此,让我们更详细地研究WebAPI项目。在这里,我有一个类别模型。

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

    public string Description { get; set; }

    public string Parent { get; set; }
}


类别模型仅包含名称和描述(ID为
TableServiceEntity)。如果类别嵌套,则还会将字符串引用添加到父类别。出现第一个问题:父级应该是类别类型而不是字符串类型,后端的类别模型是否应该具有子类别的集合?

然后,我有了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表服务实体属性。 RowKey实际上可以重命名为Id。但实际上并不相关。

主视图的ViewModel如下所示:

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


它从我创建的ViewModel扩展而来,用于共享包含Category Observable集合的页面的逻辑。此ViewModel中的重要内容:


IBudgetTrackerDataService,已注入到ViewModel中,这是一个高级数据服务
一个ObservableCollection,其中包含以下类别的集合:
ViewModel包装的类别
绑定的一些属性(例如: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);
}


长话短说:


为什么在嵌套呼叫时这些呼叫失败?
其次,我是否很愚蠢,应该处理父母/子女
笼子的关系不同?

最佳答案

我现在暂时避开父母/子女关系问题,只解决async问题。

首先,我在async/async intro blog post中详细解释了await代码的一些通用准则:


避免使用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()),但这并不总是可能的,特别是如果您为VM使用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等待其“子” VM初始化。目前尚不清楚这是您想要的,但是我假设您希望IsLoadingCategories成为true,直到所有子VM都已加载:

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,因此如果/初始化失败,则不会自动发出通知,因此您必须显式显示它。

关于c# - Window 8应用程序:嵌套异步调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14115461/

相关文章:

c# - 如何最有效地在 C 和 C#(Mono 和 Silverlight)之间共享代码

c# - 在 Dispatcher.RunAsync() 中使用 "await"会引发异常

visual-studio-2012 - 在 Windows 8.1 下以域用户和管理员身份运行 Visual Studio

java - xuggle-utils.jar 的 Ivy 存储库在哪里?

c# - 您如何通过 C# 代码处理日期字段中的 NULL?

c# - Entity Framework 核心 ObjectContext.Refresh 等效项

c# - 自定义流利验证器

xaml - WinRT 中的全局调度程序?

java - Spring 4 + Spring 存储库 + hibernate 5 : Error create java config

java - 存储库和服务层之间的区别