WPF中获取MainWindow引用的跨线程行为差异技术问询
Application.Current.MainWindow和直接字段引用获取主窗口会有跨线程行为差异? 这个问题的核心其实不在MainWindow对象本身,而是**Application.Current.MainWindow的属性 getter 内部做了线程亲和性检查**,而你自己存储的字段引用只是直接获取对象实例,不会触发这个检查。我来拆解一下具体原因:
1. Application类的线程绑定特性
Application是DispatcherObject的子类,它从诞生起就和创建它的UI线程绑定死了。当你在非UI线程访问它的任何属性时,其内部的属性访问器会自动调用VerifyAccess()方法——这个方法的作用就是检查当前线程是不是创建Application的UI线程,如果不是直接抛出InvalidOperationException。
你在后台线程调用Application.Current.MainWindow时,其实还没拿到MainWindow的引用,就先触发了Application的线程检查,这就是报错的根源。
2. 直接字段引用不会触发线程检查
你在构造函数里把this赋值给mainWindowField,这只是把对象的引用存在了一个普通CLR字段里。WPF的DispatcherObject(Window继承自它)的线程检查逻辑只在访问依赖属性、调用需要UI线程的方法时才会触发——单纯获取对象引用、访问普通的CLR字段/属性(比如你的someFlag)是完全不受限的,因为这些操作不涉及WPF的UI调度系统。
这就是为什么你能在后台线程里安全读取mainWindowField,甚至访问它的普通字段都没问题。
3. 同一个对象,不同的访问路径
你用ReferenceEquals确认是同一个实例完全正确,但两种获取方式的路径天差地别:
- 走
Application.Current.MainWindow:先碰Application对象,触发它的线程检查,直接报错。 - 走
mainWindowField:直接拿存储的引用,跳过了Application的检查,只要你不碰依赖属性或UI方法,就不会触发线程检查。
举个例子,如果你在后台线程里通过mainWindowField访问它的Title属性(这是个依赖属性),马上就会抛出和之前一样的InvalidOperationException——这才是WPF对象真正的线程亲和性限制,而单纯的引用获取是不做限制的。
结合你的代码验证
IReliablyExcept方法:后台线程访问Application.Current.MainWindow,触发Application的线程检查,直接报错。ButIDont方法:直接读mainWindowField,只是获取引用,无检查,所以正常。IDontEither方法:访问someFlag是普通字段,不需要检查;修改时用Dispatcher.Invoke切回UI线程,也符合规则。
内容的提问来源于stack exchange,提问作者schulmaster

