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); } } }
问题根因
OnBackButtonPressed是同步重写方法,内部用Device.InvokeOnMainThreadAsync投递的异步lambda属于发后即忘逻辑,返回的Task没有被等待。全速运行时,后续Shell.Current.GoToAsync触发导航后,当前页面资源会被系统回收,还没执行完的SetLockedAsync异步任务会被直接中断,导致蓝牙写入逻辑没跑完;加断点时人为拖慢执行时序,蓝牙操作在页面回收前就执行完成,所以表现正常。- 已经在
OnBackButtonPressed返回true代表完全接管系统返回逻辑,不需要再调用base.OnBackButtonPressed()。调用base方法会触发系统默认的页面销毁流程,和自定义导航逻辑产生时序冲突,加速页面资源释放。 - 贴出的控制台日志里输出的是
BluetoothHelper: SetLockedAsync(True),但返回逻辑里调用的是SetLockedAsync(false),说明看到的日志根本不是这次返回操作触发的调用,返回时触发的异步任务还没走到第一行日志打印就被中断了,直接印证了时序问题。 - 安卓蓝牙的
outputStream.Write是同步阻塞IO,直接在主线程调用本身就存在ANR风险,页面销毁时主线程任务优先级降低,IO操作调度优先级被压低,更容易被跳过。
修复方案
- 移除
base.OnBackButtonPressed()调用,既然已经接管返回逻辑就不要触发系统默认返回行为。 - 不要用发后即忘的方式在主线程队列投递异步任务,把确认弹窗、蓝牙操作、导航逻辑拆分到独立异步方法,蓝牙IO操作放到线程池执行,避免阻塞主线程,也避免页面销毁时主线程任务被清理。
- 修正后的页面返回逻辑参考:
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"); }
- 可选优化:给
SetLockedAsync增加取消令牌参数,页面销毁时如果任务还在运行可以主动取消,避免资源泄漏。
内容的提问来源于stack exchange,提问作者leander98
相关产品推荐
相关产品推荐

