Xamarin.Forms异步方法无法获取MainPage控件实例解决方案
问题原因
你代码出现无限重启的核心问题是:
- 每次执行
var mainpage = new Views.MainPage()都会创建一个全新的MainPage实例,而MainPage的构造函数中会直接调用bth.Start()启动蓝牙循环任务 - 新启动的蓝牙循环运行到发送数据的逻辑时,又会再次new MainPage,再次触发新的Start调用,形成无限递归,导致任务反复重启
- 你new出来的MainPage并不是当前屏幕上正在显示的页面实例,上面的JoystickControl没有完成加载渲染,根本拿不到真实的摇杆操作值
修复方案
不要在蓝牙逻辑类中主动创建MainPage实例,直接复用当前已经显示的MainPage实例即可,推荐用传参的方式实现:
- 首先修改Bth类,持有当前MainPage的引用,删除所有new MainPage的逻辑
class Bth : IBth { // 存储当前真实显示的MainPage实例 private MainPage _mainPage; private CancellationTokenSource _ct { get; set; } const int RequestResolveError = 1000; // 修改Start方法,接收MainPage实例参数 public void Start(MainPage mainPage, string name, int sleepTime = 200, bool readAsCharArray = false) { _mainPage = mainPage; Task.Run(async () => loop(name, sleepTime, readAsCharArray)); } private async Task loop(string name, int sleepTime, bool readAsCharArray) { BluetoothDevice device = null; BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter; BluetoothSocket BthSocket = null; _ct = new CancellationTokenSource(); while (_ct.IsCancellationRequested == false) { try { await Task.Delay(sleepTime); // 替换Thread.Sleep,避免阻塞线程池 adapter = BluetoothAdapter.DefaultAdapter; // 原有蓝牙扫描、连接逻辑保持不变 // ... if (BthSocket.IsConnected) { // 原有连接成功提示逻辑保持不变 // ... while (_ct.IsCancellationRequested == false) { string joystickData = ""; // 访问UI控件必须切到主线程 MainThread.BeginInvokeOnMainThread(() => { // 这里替换成你JoystickControl实际暴露值的属性,比如HorizontalValue、VerticalValue joystickData = $"{_mainPage.JoystickControlElement.X},{_mainPage.JoystickControlElement.Y}"; }); var bytes = Encoding.ASCII.GetBytes($"{{{joystickData}}}-"); await BthSocket.OutputStream.WriteAsync(bytes, 0, bytes.Length); await Task.Delay(100); } } } catch(Exception ex) { MainThread.BeginInvokeOnMainThread(() => { service.toast.toastSuccess(ex.Message); }); } finally { if (BthSocket != null) BthSocket.Close(); device = null; adapter = null; } } } public async Task<string> GetData() { string data = ""; MainThread.BeginInvokeOnMainThread(() => { data = $"{_mainPage.JoystickControlElement.X},{_mainPage.JoystickControlElement.Y}"; }); return data; } // 原有Cancel、PairedDevices方法保持不变 // ... }
- 然后修改MainPage构造函数中调用Start的逻辑,把当前页面实例传进去
public partial class MainPage : ContentPage { public static Bluetooth.IBth bth = new Bluetooth.Bth(); public MainPage() { InitializeComponent(); // 传入当前页面实例this,不要新创建 Task.Run(async () => bth.Start(this, "mecanumWheelRobot", 200, false)); } public CustomControls.JoystickControl JoystickControlElement { get { return JoystickControl; } } public CustomControls.JoystickControl JoystickControlElement1 { get { return JoystickControl1; } } }
其他注意事项
- 所有UI控件的属性读取、方法调用必须在主线程执行,不能直接在后台Task中访问JoystickControl,否则会触发跨线程访问UI的异常
- 后台异步循环中不要用
Thread.Sleep(),会阻塞线程池线程,优先用await Task.Delay()实现等待 - 不要在业务逻辑层随意创建Page实例,尤其是Page构造函数中包含启动逻辑的场景,很容易出现无限递归、资源泄漏问题
内容的提问来源于stack exchange,提问作者Jakob Ofner
相关产品推荐
相关产品推荐

