MAUI中Await调用首次后不再等待弹窗按钮点击问题排查
我需要实现一个弹窗控件(ContentView),包含2个输入字段和Next、Cancel两个按钮,要求用户点击任一按钮前弹窗保持打开状态。代码用async/await等待用户操作结果,但仅首次运行时会等待用户操作,后续打开弹窗时,后续代码会直接执行。
调试发现:首次调用时,返回任务的状态为Waiting for Activation,第二次及之后状态变为RanToCompletion。相同代码在Xamarin.Forms中正常运行,但在MAUI中失效。
核心问题出在ConfirmationViewModel的TaskCompletionSource<bool> AgreedSource字段上——它是类级别的成员变量,首次调用时会被设置为完成状态,后续复用同一个ViewModel实例时,这个TaskCompletionSource已经处于RanToCompletion状态,await会直接返回之前的结果,不会等待新的用户操作。
MAUI的视图/ViewModel复用机制和Xamarin.Forms存在差异,大概率是你的_viewFactoryService.CreatePopupPage方法在后续调用时没有创建新的ConfirmationViewModel实例,而是复用了之前的实例,导致AgreedSource未被重置。
要确保每次打开弹窗时,TaskCompletionSource都是全新的、未完成的状态,可从以下几点调整:
1. 重置TaskCompletionSource(推荐)
修改ConfirmationViewModel,将AgreedSource改为可重置的私有字段,新增重置方法,确保每次弹窗打开前重新初始化:
public class ConfirmationViewModel : ViewModelBase, IAnswer<bool> { private readonly INavigationService _navigationService; private TaskCompletionSource<bool> _agreedSource; public TaskCompletionSource<bool> AgreedSource { get => _agreedSource ??= new TaskCompletionSource<bool>(); } public ConfirmationViewModel(INavigationService navigationService) { _navigationService = navigationService; } // 新增重置方法,在弹窗打开前调用 public void ResetTaskCompletionSource() { _agreedSource = new TaskCompletionSource<bool>(); } }
然后在DisplayPopup方法中,获取ViewModel后调用重置方法:
public async Task<TOut> DisplayPopup<TViewModel, TOut>(object navigationData = null) where TViewModel : TOut where TOut : class { try { using (await _asyncLock.LockAsync()) { var popupPage = await _viewFactoryService.CreatePopupPage(typeof(TViewModel), navigationData); await _popupNavigation.PushAsync(popupPage); if (_deviceContext == null) { _deviceContext = DeviceContext.Instance; } _lastScannerState = _deviceContext.ScannerMgr.ScannerEnabled; _deviceContext.ScannerMgr.ScannerEnabled = false; // 重置TaskCompletionSource if (popupPage.BindingContext is ConfirmationViewModel vm) { vm.ResetTaskCompletionSource(); } if (popupPage.BindingContext is TOut returnValue) return returnValue; return popupPage.Content.BindingContext as TOut; } } catch (Exception e) { throw; } }
2. 确保每次创建新的ViewModel实例
检查_viewFactoryService.CreatePopupPage方法,确认它每次调用都会创建全新的AddDeliveryPopupViewModel(及对应的ConfirmationViewModel)实例,而非从缓存中复用。如果存在缓存逻辑,需移除或调整为每次弹窗都新建实例。
3. 完善按钮点击逻辑
确保弹窗的Next/Cancel按钮点击时,正确设置AgreedSource的结果并关闭弹窗:
// 在ConfirmationViewModel中添加按钮命令 public ICommand NextCommand => new Command(() => { AgreedSource.TrySetResult(true); _navigationService.PopPopupAsync(); }); public ICommand CancelCommand => new Command(() => { AgreedSource.TrySetResult(false); _navigationService.PopPopupAsync(); });
按钮点击时的UI调用代码
private async Task CreateDelivery(Supplier selectedSupplier) { var res = false; if(configData.EnableDeliveryIDEditpopup) { IsBusy = false; res = await _navigationService.GoToAddDeliveryPopupView(selectedSupplier); OnPropertyChanged(nameof(selectedSupplier)); if (!res) return; } var delivery = new Delivery(); delivery.deliveryDate = DateTime.Now; delivery.supplierId = selectedSupplier.SupplierId; delivery.supplierType = Convert.ToInt32(selectedSupplier.SupplierTypeId); delivery.deliveryId = selectedSupplier.DeliveryId; var result = await _deliveryNoteService.AddDeliveryAsync(delivery); if (result != null && result.deliveryNo != null) GoToDeliveryNote(result); else { var response = await BaseNavigationService.DisplayTwoOptionsPopup( new TwoOptionsPopupDto { Title = "Error", Text = result.message, RightOptionText = "Ok" }); if(response) await CreateDelivery(selectedSupplier); } }
GoToAddDeliveryPopupView方法
public async Task<bool> GoToAddDeliveryPopupView(Supplier SupplierName) { var result = await _navigationService.DisplayPopup<AddDeliveryPopupViewModel, ConfirmationViewModel>(SupplierName); return await result.AgreedSource.Task; //首次状态为Waiting for Activation,后续为RanToCompletion }
DisplayPopup方法
public async Task<TOut> DisplayPopup<TViewModel, TOut>(object navigationData = null) where TViewModel : TOut where TOut : class { try { using (await _asyncLock.LockAsync()) { var popupPage = await _viewFactoryService.CreatePopupPage(typeof(TViewModel), navigationData); await _popupNavigation.PushAsync(popupPage); if (_deviceContext == null) { _deviceContext = DeviceContext.Instance; } _lastScannerState = _deviceContext.ScannerMgr.ScannerEnabled; _deviceContext.ScannerMgr.ScannerEnabled = false; if (popupPage.BindingContext is TOut returnValue) return returnValue; return popupPage.Content.BindingContext as TOut; } } catch (Exception e) { throw; } }
ConfirmationViewModel类
public class ConfirmationViewModel : ViewModelBase, IAnswer<bool> { private readonly INavigationService _navigationService; public TaskCompletionSource<bool> AgreedSource = new TaskCompletionSource<bool>(); private bool _result; public ConfirmationViewModel(INavigationService navigationService) { _navigationService = navigationService; } }
内容的提问来源于stack exchange,提问作者Saurabh

