Xamarin Forms中WPF Dispatcher.PushFrame的等价实现方案咨询
在Xamarin.Forms中实现类似WPF的WaitWithPumping/ResultWithPumping扩展方法
针对你的遗留项目场景——Lua脚本引擎需要在UI线程同步调用异步方法,且第三方库强制要求UI线程执行任务,无法修改现有同步架构,这里提供适配iOS和Android平台的扩展方法,实现和WPF中DispatcherFrame相同的效果:在等待异步任务时手动泵消息循环,避免UI阻塞。
扩展方法实现
using System; using System.Threading.Tasks; using Xamarin.Forms; #if __IOS__ using Foundation; #endif #if __ANDROID__ using Android.OS; #endif public static class TaskPumpingExtensions { /// <summary> /// 在UI线程等待异步任务完成,同时泵消息循环避免UI阻塞 /// </summary> public static void WaitWithPumping(this Task task) { if (task == null) throw new ArgumentNullException(nameof(task)); // 任务已完成直接等待,跳过消息循环 if (task.IsCompleted) { task.Wait(); return; } bool isCompleted = false; // 在UI线程上下文标记任务完成 task.ContinueWith(_ => isCompleted = true, TaskScheduler.FromCurrentSynchronizationContext()); // 启动平台特定的消息循环泵 PumpMessageLoopUntil(() => isCompleted); // 确保任务完成后处理异常 task.Wait(); } /// <summary> /// 在UI线程等待异步任务并获取结果,同时泵消息循环避免UI阻塞 /// </summary> public static T ResultWithPumping<T>(this Task<T> task) { if (task == null) throw new ArgumentNullException(nameof(task)); // 任务已完成直接返回结果 if (task.IsCompleted) return task.Result; bool isCompleted = false; T result = default; // 在UI线程上下文获取结果并标记完成 task.ContinueWith(t => { result = t.Result; isCompleted = true; }, TaskScheduler.FromCurrentSynchronizationContext()); // 启动平台特定的消息循环泵 PumpMessageLoopUntil(() => isCompleted); return result; } private static void PumpMessageLoopUntil(Func<bool> exitCondition) { switch (Device.RuntimePlatform) { case Device.iOS: PumpIosMessageLoop(exitCondition); break; case Device.Android: PumpAndroidMessageLoop(exitCondition); break; default: throw new PlatformNotSupportedException($"当前平台{Device.RuntimePlatform}暂不支持该消息循环泵实现"); } } #if __IOS__ private static void PumpIosMessageLoop(Func<bool> exitCondition) { var currentRunLoop = NSRunLoop.Current; var defaultMode = NSRunLoop.DefaultMode; // 持续处理消息直到任务完成,每次处理0.01秒平衡响应性和性能 while (!exitCondition() && currentRunLoop.RunUntil(NSDate.FromTimeIntervalSinceNow(0.01))) { } } #endif #if __ANDROID__ private static void PumpAndroidMessageLoop(Func<bool> exitCondition) { var looper = Looper.MyLooper(); var messageQueue = looper.MessageQueue; while (!exitCondition()) { // 取出并处理队列中的消息 if (messageQueue.Next(out var message)) { try { looper.Target.DispatchMessage(message); } finally { message.Recycle(); } } } } #endif }
核心原理说明
- 和WPF的
DispatcherFrame思路一致:通过手动处理UI线程的消息队列,让UI在等待异步任务时仍能响应用户交互,不会出现卡顿。 - 针对iOS和Android的消息循环机制分别实现:
- iOS:利用
NSRunLoop的RunUntil方法,每次处理一小段时间的消息后检查任务是否完成。 - Android:直接操作
Looper和MessageQueue,逐个处理队列中的消息直到任务完成。
- iOS:利用
- 先判断任务是否已完成,避免不必要的消息循环开销,同时在任务完成后通过
task.Wait()确保异常被正确抛出。
用法示例(和你WPF中的代码完全兼容)
public async void RunTaskOnUi1Async() { await ProcessAsync(); UpdateUiInner(); } public async object RunTaskOnUi2Async() { var result = await ProcessAsync(); UpdateUiInner(result); return result; } // 在UI线程中调用(比如Lua引擎的回调) public void RunTasks() { UpdateUi(); RunTaskOnUi1Async().WaitWithPumping(); UpdateUi(); var result = RunTaskOnUi2Async().ResultWithPumping(); UpdateUi(result); }
注意事项
- 必须在UI线程调用:这些扩展方法依赖UI线程的消息循环,在后台线程调用会导致异常。
- 避免嵌套调用:不要在
WaitWithPumping/ResultWithPumping内部再调用同类方法,可能导致消息循环死锁。 - 性能考量:长时间运行的异步任务仍可能影响UI响应,若有条件建议逐步优化异步架构,但针对遗留项目这个方案能满足需求。
内容的提问来源于stack exchange,提问作者hthms
相关产品推荐
相关产品推荐

