MAUI应用中使用async/await重构构造函数的方式是否正确?
我读了不少文章,但还是搞不懂。我想在MAUI应用加载数据时保持UI可响应。
我原本想这么写代码:
async public DataService() { //initialize stuff here await this.GetPayees(); await this.GetCategories(); return; }
后来知道不能有异步构造函数,就重新设计了初始化方式:
public DataService() { //Take this out here //this.GetPayees(); //this.GetCategories(); return; } async public static Task<DataService> BuildDataServiceAsync() { //await them here var dataService = new DataService(); await dataService.GetPayees(); await dataService.GetCategories(); return dataService; }
这导致连锁反应,我得把很多方法改成异步,返回Task类型:
async public Task<List<Payee>> GetPayees() { //Load arbitrary data, if(Payees.Count != 0) return Payees; Payees.Add(new Payee { Id = 0, Name = "Food Lion", DefaultCategoryId = 0, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 1, Name = "Work Incorporated", DefaultCategoryId = 1, DefaultIsCredit = true }); Payees.Add(new Payee { Id = 2, Name = "Hardees", DefaultCategoryId = 3, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 3, Name = "Wal-Mart", DefaultCategoryId = 5, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 4, Name = "Aldis", DefaultCategoryId = 0, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 5, Name = "McDonalds", DefaultCategoryId = 3, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 6, Name = "Harris Teeter", DefaultCategoryId = 0, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 7, Name = "Shoe Show", DefaultCategoryId = 2, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 8, Name = "Capital One", DefaultCategoryId = 4, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 9, Name = "Dicks Sporting Goods", DefaultCategoryId = 6, DefaultIsCredit = false }); Payees.Add(new Payee { Id = 10, Name = "Amazon", DefaultCategoryId = 7, DefaultIsCredit = false }); return Payees; } async public Task<List<Category>> GetCategories() { if(Categories.Count != 0) return Categories; Categories.Add(new Category { Id = 0, Name = "Groceries" }); Categories.Add(new Category { Id = 1, Name = "Paycheck" }); Categories.Add(new Category { Id = 2, Name = "Shoes" }); Categories.Add(new Category { Id = 3, Name = "Fast Food" }); Categories.Add(new Category { Id = 4, Name = "Credit Card" }); Categories.Add(new Category { Id = 5, Name = "Supplies" }); Categories.Add(new Category { Id = 6, Name = "Recreation" }); Categories.Add(new Category { Id = 7, Name = "Grocery" }); Categories.Add(new Category { Id = 8, Name = "Gross" }); Categories.Add(new Category { Id = 9, Name = "Grass" }); return Categories; }
现在编译器警告这两个方法里没有await语句,难道我连Add()操作都要加await吗?
刚把依赖调顺,结果ViewModel也受影响了,它注入了这些服务:
public PayeesViewModel(DataService dataService, NavigationService navigationService, ValidationService validationService) { this.dataService = dataService; this.navigationService = navigationService; this.validationService = validationService; Payees = await dataService.GetPayees(); Categories = await dataService.GetCategories(); for(int x = 0; x < Payees.Count; x++) { PayeeDisplay.Add(new PayeeDisplay { Id = Payees[x].Id, Name = Payees[x].Name, DefaultCategory = Categories.Find(c => c.Id.Equals(Payees[x].DefaultCategoryId)).Name, DefaultCategoryId = Payees[x].DefaultCategoryId, DefaultIsCredit = Payees[x].DefaultIsCredit }); } }
我是不是方法用错了?搞个async/await快把项目搞崩了。
解决方案
1. 消除无await的异步方法警告
你现在的GetPayees和GetCategories全是同步代码,完全没必要加async关键字。只有当方法内部需要调用真正的异步操作(比如从API、数据库读取数据)时,才需要改成异步方法。现在直接把方法改回同步:
public List<Payee> GetPayees() { if(Payees.Count != 0) return Payees; // 原Add代码保持不变 return Payees; } public List<Category> GetCategories() { if(Categories.Count != 0) return Categories; // 原Add代码保持不变 return Categories; }
对应的初始化方法也可以简化,因为调用同步方法不需要await:
public static DataService BuildDataService() { var dataService = new DataService(); dataService.GetPayees(); dataService.GetCategories(); return dataService; }
等以后需要接入异步数据源时,再把这些方法改成async Task<T>,并在内部用await调用异步API即可。
2. ViewModel的异步初始化处理
构造函数里不能直接用await,有两种常用解决办法:
方法一:新增异步初始化方法
给ViewModel添加一个专门的初始化方法,在ViewModel创建后调用:
public PayeesViewModel(DataService dataService, NavigationService navigationService, ValidationService validationService) { this.dataService = dataService; this.navigationService = navigationService; this.validationService = validationService; } public async Task InitializeAsync() { Payees = dataService.GetPayees(); // 以后改成异步操作时,这里加await Categories = dataService.GetCategories(); foreach(var payee in Payees) { var category = Categories.Find(c => c.Id == payee.DefaultCategoryId); PayeeDisplay.Add(new PayeeDisplay { Id = payee.Id, Name = payee.Name, DefaultCategory = category?.Name ?? string.Empty, DefaultCategoryId = payee.DefaultCategoryId, DefaultIsCredit = payee.DefaultIsCredit }); } }
然后在MAUI页面的OnAppearing生命周期方法中调用这个初始化方法:
protected override async void OnAppearing() { base.OnAppearing(); if (BindingContext is PayeesViewModel vm) { await vm.InitializeAsync(); } }
方法二:使用异步工厂方法
如果希望ViewModel创建时就完成初始化,可以仿照DataService的做法,用静态异步工厂方法:
// 把构造函数设为私有,强制通过工厂方法创建实例 private PayeesViewModel(DataService dataService, NavigationService navigationService, ValidationService validationService) { this.dataService = dataService; this.navigationService = navigationService; this.validationService = validationService; } public static async Task<PayeesViewModel> CreateAsync(DataService dataService, NavigationService navigationService, ValidationService validationService) { var vm = new PayeesViewModel(dataService, navigationService, validationService); vm.Payees = dataService.GetPayees(); vm.Categories = dataService.GetCategories(); foreach(var payee in vm.Payees) { var category = vm.Categories.Find(c => c.Id == payee.DefaultCategoryId); vm.PayeeDisplay.Add(new PayeeDisplay { Id = payee.Id, Name = payee.Name, DefaultCategory = category?.Name ?? string.Empty, DefaultCategoryId = payee.DefaultCategoryId, DefaultIsCredit = payee.DefaultIsCredit }); } return vm; }
这种方式需要在依赖注入注册或ViewModel创建的地方,调用CreateAsync方法来获取实例。
3. 保持UI响应的核心
如果以后数据加载是真正的异步操作(比如网络请求、数据库查询),一定要用await,这样不会阻塞UI线程。现在你的数据是本地模拟的同步代码,本身就不会卡UI,没必要强行套异步的壳。
内容的提问来源于stack exchange,提问作者XJonOneX

