You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:22:06