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

XAML.cs重写OnBackButtonPressed时如何正确调用异步依赖服务

.NET MAUI 安卓返回键触发蓝牙方法偶发不执行问题

问题表现

  • 重写页面OnBackButtonPressed()事件实现自定义返回逻辑:用户按安卓系统返回三角键时,先弹二次确认框,确认后调用安卓平台实现的经典蓝牙依赖服务方法,再跳转到指定页面。当前页面为关键业务流程,已禁用导航栏自带返回箭头,强制要求二次确认才能退出。
  • 异常现象:只有在蓝牙方法内加断点拖慢执行流程时,蓝牙依赖方法才能正常调用;无断点全速运行时蓝牙方法调用会被跳过,初步怀疑是async/await配置或线程调度问题。
  • 补充说明:当前对接设备的经典蓝牙命令协议存在设计缺陷,但无修改权限;如果从零实现蓝牙控制器会采用更规范的set/get方法,但现阶段无法调整现有协议,本身也不会选择经典蓝牙实现该场景需求。
  • 调用链路:页面代码后台调用Helper类,Helper中转调用平台蓝牙依赖服务,这套调用逻辑在ViewModel中执行完全正常。

已验证信息

  • 所有蓝牙相关方法在MVVM架构的ViewModel中调用时运行完全正常
  • 调试过程中未捕获到任何异常信息
  • 最初误以为Task.Delay()调用被跳过、方法未按异步逻辑执行,核对日志毫秒时间戳后确认延迟逻辑本身运行正常

相关代码

页面后台代码

public partial class ProcessExecutionPage : ContentPage
{
    public ProcessExecutionPage()
    {
        InitializeComponent();
    }

    protected override bool OnBackButtonPressed()
    {
        Device.InvokeOnMainThreadAsync(async () =>
        {
            // 绑定ViewModel相关操作,运行正常

            if (await DisplayAlert("返回", "确认返回吗?", "是", "否"))
            {
                await BluetoothHelper.SetLockedAsync(false); // 该方法无法正常调用

                base.OnBackButtonPressed();

                // 更新数据库等操作,运行正常

                await Shell.Current.GoToAsync("MyNextPage"); // 导航操作运行正常
            }
        });
        return true;
    }
}

BluetoothHelper 实现

该方法用于查询蓝牙设备状态并完成对应设置:

public static async Task<bool> SetLockedAsync(bool status)
{
    int delay = 300;
    System.Console.WriteLine("BluetoothHelper: SetLockedAsync(" + status.ToString() + ")");
    // 状态检查
    App.BluetoothService.Write("*");
    System.Console.WriteLine("Writing * - " + DateTime.Now.Millisecond.ToString());
    await Task.Delay(delay);
    
    int messageCounter = App.MessageList.Count;
    System.Console.WriteLine("messageCounter: " + messageCounter.ToString());
    App.BluetoothService.Write("*");
    System.Console.WriteLine("Writing * - " + DateTime.Now.Millisecond.ToString());
    await Task.Delay(delay);

    bool lockedStatus;
    System.Console.WriteLine("Getting lockedStatus - " + DateTime.Now.Millisecond.ToString());
    if (App.MessageList.Count > messageCounter) lockedStatus = false;
    else lockedStatus = true;

    System.Console.WriteLine("Check locked status");
    if (status != lockedStatus)
    {
        System.Console.WriteLine("Writing * - " + DateTime.Now.Millisecond.ToString());
        App.BluetoothService.Write("*");
        await Task.Delay(delay);
    }

    return true;
}

控制台输出日志

BluetoothHelper: SetLockedAsync(True)
Writing * - 167
Bluetooth: Listening received: 10
messageCounter: 7
Writing * - 507
Getting lockedStatus - 856
Check locked status

蓝牙服务接口定义

public interface IBluetoothService
{
    //...
    void Write(string text);
    //...
}

安卓平台蓝牙实现代码

public class AndroidBluetoothService : IBluetoothService
{
    /*私有字段定义*/
    private BluetoothDevice device;
    private string connectedDeviceName;        
    private BluetoothSocket socket;
    private Stream outputStream;
    //...

    /*公共属性定义*/
    public bool IsConnected { get; private set; }
    //...

    public void Write(string text)
    {
        if (!IsConnected || connectedDeviceName == string.Empty || device == null || socket == null) return;

        byte[] bytes = Encoding.UTF8.GetBytes(text);
        try
        {
            outputStream.Write(bytes, 0, bytes.Length);
        }
        catch(System.Exception ex) { System.Console.WriteLine("Bluetooth: ERROR writing message: " + ex.Message); }
    }
}

问题根因

  1. OnBackButtonPressed是同步重写方法,内部用Device.InvokeOnMainThreadAsync投递的异步lambda属于发后即忘逻辑,返回的Task没有被等待。全速运行时,后续Shell.Current.GoToAsync触发导航后,当前页面资源会被系统回收,还没执行完的SetLockedAsync异步任务会被直接中断,导致蓝牙写入逻辑没跑完;加断点时人为拖慢执行时序,蓝牙操作在页面回收前就执行完成,所以表现正常。
  2. 已经在OnBackButtonPressed返回true代表完全接管系统返回逻辑,不需要再调用base.OnBackButtonPressed()。调用base方法会触发系统默认的页面销毁流程,和自定义导航逻辑产生时序冲突,加速页面资源释放。
  3. 贴出的控制台日志里输出的是BluetoothHelper: SetLockedAsync(True),但返回逻辑里调用的是SetLockedAsync(false),说明看到的日志根本不是这次返回操作触发的调用,返回时触发的异步任务还没走到第一行日志打印就被中断了,直接印证了时序问题。
  4. 安卓蓝牙的outputStream.Write是同步阻塞IO,直接在主线程调用本身就存在ANR风险,页面销毁时主线程任务优先级降低,IO操作调度优先级被压低,更容易被跳过。

修复方案

  1. 移除base.OnBackButtonPressed()调用,既然已经接管返回逻辑就不要触发系统默认返回行为。
  2. 不要用发后即忘的方式在主线程队列投递异步任务,把确认弹窗、蓝牙操作、导航逻辑拆分到独立异步方法,蓝牙IO操作放到线程池执行,避免阻塞主线程,也避免页面销毁时主线程任务被清理。
  3. 修正后的页面返回逻辑参考:
protected override bool OnBackButtonPressed()
{
    // 丢弃返回值不等待,符合重写方法同步签名的要求
    _ = HandleBackPressAsync();
    return true;
}

private async Task HandleBackPressAsync()
{
    // 弹窗需要在主线程调用,无需额外切换线程
    bool confirm = await DisplayAlert("返回", "确认返回吗?", "是", "否");
    if (!confirm) return;

    try
    {
        // 蓝牙阻塞IO放到线程池执行,避免主线程阻塞和任务被回收
        await Task.Run(() => BluetoothHelper.SetLockedAsync(false).Wait());
    }
    catch (Exception ex)
    {
        Console.WriteLine($"蓝牙操作异常: {ex.Message}");
    }

    // 数据库更新等原有逻辑保留
    await Shell.Current.GoToAsync("MyNextPage");
}
  1. 可选优化:给SetLockedAsync增加取消令牌参数,页面销毁时如果任务还在运行可以主动取消,避免资源泄漏。

内容的提问来源于stack exchange,提问作者leander98

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:48:27