C# windows8应用程序:嵌套异步调用

C# windows8应用程序:嵌套异步调用,c#,windows-8,repository,mvvm-light,async-await,C#,Windows 8,Repository,Mvvm Light,Async Await,我正在构建一个Windows8应用程序,但在异步调用方面遇到了问题。我将尽力提供尽可能多的细节,因为我认为这有两个结果: 在异步调用方面,我做的事情是完全错误的 或者我做错了,但也可能是错误的架构让我遇到了这个问题,而这个问题本来就不应该存在。 我是WindowsAzure和MVVM的新手,但情况如下 该应用程序现在是为Windows8构建的,但我也希望能够使用其他平台,因此我首先要做的是创建一个WebAPI项目,该项目将发布到WindowsAzure网站。这样,我就可以使用JSON传输数据,W

我正在构建一个Windows8应用程序,但在异步调用方面遇到了问题。我将尽力提供尽可能多的细节,因为我认为这有两个结果:

在异步调用方面,我做的事情是完全错误的 或者我做错了,但也可能是错误的架构让我遇到了这个问题,而这个问题本来就不应该存在。 我是WindowsAzure和MVVM的新手,但情况如下

该应用程序现在是为Windows8构建的,但我也希望能够使用其他平台,因此我首先要做的是创建一个WebAPI项目,该项目将发布到WindowsAzure网站。这样,我就可以使用JSON传输数据,WebAPI控制器连接到一个存储库,该存储库正在处理与WindowsAzure表存储之间的数据请求。第二部分是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是 表服务实体。此外,如果类别嵌套,则会将字符串引用添加到父类别中。第一个问题出现了:父类是否应该是Category而不是string类型,后端的Category模型是否应该有子类的集合

然后我有我的IRepository接口来定义存储库。正在进行的工作它还使用规范模式传递查询范围。您可以使用浏览器进行测试并浏览到:

因此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);
}
长话短说:

为什么嵌套调用时这些调用失败? 第二,我是否愚蠢,我是否应该处理好父母/孩子 鸟笼之间的关系不同?
我现在要避开父/子关系问题,只解决异步问题

首先,关于异步代码,有两个通用准则,我在我的

避免异步void返回任务或任务。 如果适用,请使用ConfigureWaitFalse。 我见过其他人采用的回调委托方法,但我不确定它是从哪里来的。IMO说,它不能很好地与async一起工作,只会使代码复杂化。任务类型被设计为表示一个结果值和一个异常,它可以与wait无缝地工作

首先,您的数据服务:

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();
}
然后,您可以选择让父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; } }

我添加了InitializationError和NotifyOnInitializationErrorAsync,以演示一种显示初始化期间可能发生的任何错误的方法。由于Task未实现INotifyPropertyChanged,因此在初始化失败时没有自动通知,因此您必须显式地公开它。

我现在要回避父/子关系问题,只解决异步问题

首先,关于异步代码,有两个通用准则,我在我的

避免异步void返回任务或任务。 如果适用,请使用ConfigureWaitFalse。 我见过其他人采用的回调委托方法,但我不确定它是从哪里来的。IMO说,它不能很好地与async一起工作,只会使代码复杂化。任务类型被设计为表示一个结果值和一个异常,它可以与wait无缝地工作

首先,您的数据服务:

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();
}
然后,您可以选择让父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; } }

我添加了InitializationError和NotifyOnInitializationErrorAsync,以演示一种显示初始化期间可能发生的任何错误的方法。由于任务未实现INotifyPropertyChanged,因此在初始化失败时没有自动通知,因此您必须显式地显示它。

只是一个提示:如果您发布较短的问题,人们可能更愿意帮助您。如果你有不止一个问题,分别问他们。只是一个提示:人们可能更愿意帮助你
如果你发布简短的问题。如果你有不止一个问题,分别问他们。哇,斯蒂芬!这超出了我的期望:-巧合的是,我看到了斯科特·汉斯曼关于昨天的视频。但是你的博客帖子和你的回答告诉我如何在我的解决方案中实现它,这对我帮助很大!我将实施此功能,并让您知道它是否有效。谢谢!我正在尝试代码,但我有几个问题。首先,在CategoryViewModel的构造函数中,我看到:Initialized=InitializeAsync;但是财产只有一个get;所以是只读的,对吗?其次,这行代码:类别中的foreach var category不应该是:类别中的foreach var category.result??1该属性有一个私有setter,我在公共API描述中省略了它。但是,它是一个只读属性,仅在构造函数中设置。我忘记了等待;请参阅更新的代码。不要将Task.Result与异步代码一起使用;改用wait。我现在已经成功地让它工作了:-我这边还有一个问题:CategoryViewModel:initializeAsync方法不包含wait关键字,编译器警告它随后会同步执行?我将尝试查看父/子对象,看看我是否可以在那里做不同的事情。您应该等待对数据服务方法的调用。代码又更新了。哇,斯蒂芬!这超出了我的期望:-巧合的是,我看到了斯科特·汉斯曼关于昨天的视频。但是你的博客帖子和你的回答告诉我如何在我的解决方案中实现它,这对我帮助很大!我将实施此功能,并让您知道它是否有效。谢谢!我正在尝试代码,但我有几个问题。首先,在CategoryViewModel的构造函数中,我看到:Initialized=InitializeAsync;但是财产只有一个get;所以是只读的,对吗?其次,这行代码:类别中的foreach var category不应该是:类别中的foreach var category.result??1该属性有一个私有setter,我在公共API描述中省略了它。但是,它是一个只读属性,仅在构造函数中设置。我忘记了等待;请参阅更新的代码。不要将Task.Result与异步代码一起使用;改用wait。我现在已经成功地让它工作了:-我这边还有一个问题:CategoryViewModel:initializeAsync方法不包含wait关键字,编译器警告它随后会同步执行?我将尝试查看父/子对象,看看我是否可以在那里做不同的事情。您应该等待对数据服务方法的调用。代码再次更新。
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService
budgetTrackerDataService)
{
    _category = categoryModel;
    _dataService = budgetTrackerDataService;

    // Retrieve all the child categories for this category
    _dataService.GetCategoriesByParentAsync(_category.RowKey,
GetCategoriesByParentCompleted);
}
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);
}
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();
}
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);
}
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);
  ...
}
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; } }