UWP多窗口事件触发与UI更新异常问题咨询
先给你梳理清楚UWP多窗口的核心规则:每个ApplicationView(也就是每个窗口)都有自己独立的UI线程,所有属于这个窗口的UI元素,只能在它的创建线程上访问,跨线程直接操作必然触发封送异常。下面逐个拆解你的几种实现差异:
1. 注册每个窗口的Dispatcher+NumberCruncher实例为什么能正常运行?
你在每个MainPage导航时,把当前窗口的CoreDispatcher(绑定到该窗口的UI线程)和当前页面的NumberCruncher实例(它的NumberEvent绑定的是当前窗口的listView)一起注册到静态字典里。
当调用SendInNumber时,你遍历字典里的每一项,用对应窗口自己的Dispatcher去触发该窗口专属的NumberCruncher实例的事件。这时候事件处理器是在目标窗口的UI线程上执行的,操作的也是这个线程创建的listView,完全符合UWP的线程规则,自然不会报错。
2. 直接触发当前类的NumberEvent为什么会报错?
这里的问题出在"串线"上:你在遍历其他窗口的Dispatcher时,触发的是调用SendInNumber的那个NumberCruncher实例的事件,而这个实例的事件处理器绑定的是某个特定窗口的listView。
举个例子:假设你有窗口A和窗口B,你在窗口B调用SendInNumber,然后遍历到窗口A的Dispatcher,用A的线程去触发窗口B的NumberCruncher的事件。这时候事件处理器在A的线程上执行,却要操作窗口B的listView——这属于跨线程访问其他窗口的UI元素,直接触发封送异常。
3. 保存SynchronizationContext.Post为什么可行?
SynchronizationContext.Current在UI线程上获取的是DispatcherSynchronizationContext,它本质是对当前窗口CoreDispatcher的封装。每个MainPage实例在构造时都会保存自己窗口线程的SynchronizationContext,当事件触发时,调用Post会把更新UI的操作派发到当前MainPage所属窗口的UI线程上执行,操作的是自己线程创建的listView,完全合规。
这个方案其实和第一种思路类似,只是把"用对应Dispatcher调用"换成了用SynchronizationContext来调度,本质都是确保UI操作在正确的线程上执行。
4. 用CoreApplication.GetCurrentView().CoreWindow.Dispatcher为什么会报错?
这里的坑是CoreApplication.GetCurrentView()的行为:它返回的是当前执行代码的线程所属的窗口,而不是你要操作的listView所在的窗口!
比如,当你的NumberEvent是在窗口A的Dispatcher线程上执行时,GetCurrentView()拿到的是窗口A的View,它的Dispatcher是A的线程,但你要操作的listView属于窗口B,这就导致你用A的线程去操作B的UI元素,直接触发跨线程异常。你误以为这个API能拿到UI元素所属的窗口,但实际上它只和当前代码运行的线程绑定。
内容的提问来源于stack exchange,提问作者golf1052

