UWP CommunityToolkit Messenger异步处理报InvalidOperationException解决方案
WeakReferenceMessenger的请求消息存在硬性约束:所有注册的消息处理器必须在同步执行阶段调用Reply()方法设置返回值。
你当前给消息处理委托加了async/await后,执行到await continueCallbackAnsweringDialog.ShowAsync()时,委托会将控制权交回给Messenger的Send方法,Messenger遍历完所有处理器后发现没有任何处理器设置返回值,就会直接抛出InvalidOperationException。等弹窗交互完成、你再调用message.Reply()时,消息对象的处理生命周期已经结束,自然无法正常返回结果。
你测试移除async/await后代码可正常运行,本质就是刚好满足了Messenger「同步Reply」的要求,但这种写法无法等待异步弹窗的交互结果,和业务需求存在冲突。
不要在消息处理器内部等待异步弹窗完成,而是同步将「等待用户交互的Task」作为Reply值返回,让ViewModel侧自行等待最终结果,不要让Messenger阻塞等待异步逻辑。
调整消息注册端(Dialer.xaml.cs)逻辑
移除消息处理委托的async/await关键字,弹窗的异步等待逻辑收敛到TaskCompletionSource中,同步返回待完成的Task即可:
WeakReferenceMessenger.Default.Register<ContinueCallbackAnsweringRequestMessage>(this, (r, message) => { var continueCallbackAnsweringDialog = new MessageDialog("آیا مایل به ادامه پاسخدهی به تماسهای درخواستی هستید؟"); TaskCompletionSource<CancellationToken> tcs = new TaskCompletionSource<CancellationToken>(TaskCreationOptions.RunContinuationsAsynchronously); continueCallbackAnsweringDialog.Commands.Add(new UICommand( "بلی", new UICommandInvokedHandler((IUICommand command) => { tcs.SetResult(new CancellationToken(false)); }))); continueCallbackAnsweringDialog.Commands.Add(new UICommand( "خیر", new UICommandInvokedHandler((IUICommand command) => { tcs.SetResult(new CancellationToken(true)); }))); continueCallbackAnsweringDialog.DefaultCommandIndex = 0; continueCallbackAnsweringDialog.CancelCommandIndex = 1; // 触发弹窗显示,不阻塞当前消息处理流程 _ = continueCallbackAnsweringDialog.ShowAsync(); // 同步Reply返回待完成的Task,满足Messenger的同步返回要求 message.Reply(tcs.Task); });
说明:
_ = continueCallbackAnsweringDialog.ShowAsync();是用弃元丢弃异步操作的编译器警告,我们不需要在消息处理器中等待弹窗完成,用户点击按钮时会自动给TaskCompletionSource设置结果。
确认消息类定义兼容
你的ContinueCallbackAnsweringRequestMessage需要支持返回Task<CancellationToken>类型,对应定义参考如下:
public class ContinueCallbackAnsweringRequestMessage : RequestMessage<Task<CancellationToken>> { }
调整后ViewModel侧的CallbackClick逻辑不需要做任何修改,await拿到的结果就是用户弹窗选择后的CancellationToken,完全符合原有业务预期。
- 记得在
Dialer.xaml.cs的NavigatedFrom生命周期回调中注销对应消息,避免内存泄漏:WeakReferenceMessenger.Default.Unregister<ContinueCallbackAnsweringRequestMessage>(this); - 不要在
WeakReferenceMessenger的消息处理委托中使用async void写法,除了会导致Reply时机错误触发异常,async void未捕获的异常会直接导致应用崩溃。
内容的提问来源于stack exchange,提问作者sorosh_sabz

