windows 8应用程序:嵌套异步调用
本文关键字:异步 调用 嵌套 应用程序 windows | 更新日期: 2023-09-27 17:50:26
我正在构建一个Windows 8应用程序,我遇到了异步调用的麻烦。我将尽量提供尽可能多的细节,因为我认为这有两种结果:
- 我做的东西完全错误,当它涉及到异步调用
- 或者我做错了,但也可能是错误的架构让我遇到了这个问题,而这个问题本来就不应该存在。
我是Windows Azure和MVVM的新手,但情况是这样的…
该应用程序现在是为Windows 8构建的,但我也希望能够使用其他平台,所以我首先做的是创建一个发布到Windows Azure网站的WebAPI项目。这样,我就可以使用JSON来传输数据,WebAPI控制器连接到一个存储库,该存储库正在处理来自windows Azure表存储的数据请求。第二部分是MVVM Light Windows 8应用程序,它从Azure Web站点请求数据。
让我们更详细地了解一下WebAPI项目。这里我有一个类别模型来开始。
public class Category : TableServiceEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
public string Parent { get; set; }
}
类别模型只包含一个Name和Description (id是类别的RowKey)TableServiceEntity)。如果类别是嵌套的,还会向父类别添加一个字符串引用。第一个问题出现了:父类的类型应该是Category而不是字符串吗?后端的Category模型应该有一个子类别的集合吗?
然后我有IRepository接口来定义存储库。(工作正在进行中;-))它还使用Specification模式来传递查询范围。您可以使用浏览器进行测试,并浏览到: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();
}
现在已经清楚了存储库,让我们看一下控制器。我有一个categorescontroller它只是一个ApiController包含一个IRepository库。(注入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中重要的内容:
- 一个注入ViewModel的IBudgetTrackerDataService,它是一个高级数据服务
- 一个ObservableCollection,包含一个category-的集合li>一些用于绑定的属性(例如:IsLoadingCategories来处理视图上的ProgressRing) 一个由IBudgetTrackerDataService调用的getcategorescompleted回调方法异步调用将完成
所以代码如下:
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的构造是获取类别并调用回调方法getcategorescompleted:
_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);
}
这就是我的问题!getcategoresbyparentasync是发生在另一个异步调用内部的异步调用,代码只是跳出调用而不做任何事情。
数据服务实现接口:
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
/await
介绍博客文章中详细解释了async
代码的一些一般准则:
- 避免
async void
(返回Task
或Task<T>
代替)。 - 适用时使用
ConfigureAwait(false)
我见过其他人采用的callback
委托方法,但我不确定它来自哪里。它不能很好地与async
一起工作,只会使代码复杂化。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);
...
}
你可以选择让你的"父"虚拟机等待它的"子"虚拟机初始化。目前还不清楚这是否是您想要的,但我假设您希望IsLoadingCategories
为true
,直到所有子虚拟机都加载完成:
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
,所以如果/当初始化失败时没有自动通知,所以您必须显式地显示它。