当前位置: 首页 > news >正文

WPF 的 Application.Current.Dispatcher 中,Dispatcher 属性一定不会为 null

在 WPF 程序中,可能会存在 Application.Current.Dispatcher.Xxx 这样的代码让一部分逻辑回到主 UI 线程。因为发现在调用这句代码的时候出现了 NullReferenceException,于是就有三位小伙伴告诉我说 CurrentDispatcher 属性都可能为 null

然而实际上这里只可能 Currentnull 而此上下文的 Dispatcher 是绝对不会为 null 的。(当然我们这里讨论的是常规编程手段,如果非常规手段,你甚至可以让实例的 thisnull 呢……)


由于本文所述的两个部分都略长,所以拆分成两篇博客,这样更容易理解。

  • WPF 的 Application.Current.Dispatcher 中,Dispatcher 属性一定不会为 null
  • WPF 的 Application.Current.Dispatcher 中,为什么 Current 可能为 null

本文内容

    • `Application.Dispatcher` 实例属性
      • 源代码
      • 赋值时机
      • 后续赋值
      • 结论
    • `Application.Current` 静态属性

Application.Dispatcher 实例属性

Application.Dispatcher 实例属性来自于 DispatcherObject

源代码

为了分析此属性是否可能为 null,我现在将 DispatcherObject 的全部代码贴在下面:

using System;
using System.Windows;
using System.Threading;
using MS.Internal.WindowsBase;               // FriendAccessAllowed
 
namespace System.Windows.Threading
{
    /// <summary>
    ///     A DispatcherObject is an object associated with a
    ///     <see cref="Dispatcher"/>.  A DispatcherObject instance should
    ///     only be access by the dispatcher's thread.
    /// </summary>
    /// <remarks>
    ///     Subclasses of <see cref="DispatcherObject"/> should enforce thread
    ///     safety by calling <see cref="VerifyAccess"/> on all their public
    ///     methods to ensure the calling thread is the appropriate thread.
    ///     <para/>
    ///     DispatcherObject cannot be independently instantiated; that is,
    ///     all constructors are protected.
    /// </remarks>
    public abstract class DispatcherObject
    {
        /// <summary>
        ///     Returns the <see cref="Dispatcher"/> that this
        ///     <see cref="DispatcherObject"/> is associated with.
        /// </summary>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)]
        public Dispatcher Dispatcher
        {
            get
            {
                // This property is free-threaded.
 
                return _dispatcher;
            }
        }
 
        // This method allows certain derived classes to break the dispatcher affinity
        // of our objects.
        [FriendAccessAllowed] // Built into Base, also used by Framework.
        internal void DetachFromDispatcher()
        {
            _dispatcher = null;
        }
 
        // Make this object a "sentinel" - it can be used in equality tests, but should
        // not be used in any other way.  To enforce this and catch bugs, use a special
        // sentinel dispatcher, so that calls to CheckAccess and VerifyAccess will
        // fail;  this will catch most accidental uses of the sentinel.
        [FriendAccessAllowed] // Built into Base, also used by Framework.
        internal void MakeSentinel()
        {
            _dispatcher = EnsureSentinelDispatcher();
        }
 
        private static Dispatcher EnsureSentinelDispatcher()
        {
            if (_sentinelDispatcher == null)
            {
                // lazy creation - the first thread reaching here creates the sentinel
                // dispatcher, all other threads use it.
                Dispatcher sentinelDispatcher = new Dispatcher(isSentinel:true);
                Interlocked.CompareExchange<Dispatcher>(ref _sentinelDispatcher, sentinelDispatcher, null);
            }
 
            return _sentinelDispatcher;
        }
 
        /// <summary>
        ///     Checks that the calling thread has access to this object.
        /// </summary>
        /// <remarks>
        ///     Only the dispatcher thread may access DispatcherObjects.
        ///     <p/>
        ///     This method is public so that any thread can probe to
        ///     see if it has access to the DispatcherObject.
        /// </remarks>
        /// <returns>
        ///     True if the calling thread has access to this object.
        /// </returns>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
        public bool CheckAccess()
        {
            // This method is free-threaded.
 
            bool accessAllowed = true;
            Dispatcher dispatcher = _dispatcher;
 
            // Note: a DispatcherObject that is not associated with a
            // dispatcher is considered to be free-threaded.
            if(dispatcher != null)
            {
                accessAllowed = dispatcher.CheckAccess();
            }
 
            return accessAllowed;
        }
 
        /// <summary>
        ///     Verifies that the calling thread has access to this object.
        /// </summary>
        /// <remarks>
        ///     Only the dispatcher thread may access DispatcherObjects.
        ///     <p/>
        ///     This method is public so that derived classes can probe to
        ///     see if the calling thread has access to itself.
        /// </remarks>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
        public void VerifyAccess()
        {
            // This method is free-threaded.
 
            Dispatcher dispatcher = _dispatcher;
 
            // Note: a DispatcherObject that is not associated with a
            // dispatcher is considered to be free-threaded.
            if(dispatcher != null)
            {
                dispatcher.VerifyAccess();
            }
        }
 
        /// <summary>
        ///     Instantiate this object associated with the current Dispatcher.
        /// </summary>
        protected DispatcherObject()
        {
            _dispatcher = Dispatcher.CurrentDispatcher;
        }
 
        private Dispatcher _dispatcher;
        private static Dispatcher _sentinelDispatcher;
    }
}

代码来自:DispatcherObject.cs。

Dispatcher 属性仅仅是在获取 _dispatcher 字段的值,因此我们只需要看 _dispatcher 字段的赋值时机,以及所有给 _dispatcher 赋值的代码。

由于 _dispatcher 字段是私有字段,所以仅需调查这个类本身即可找到所有的赋值时机。(反射等非常规手段需要排除在外,因为这意味着开发者是逗比——自己闯的祸不能怪 WPF 框架。)

赋值时机

先来看看 dispatcher 字段的赋值时机。

DispatcherObject 仅有一个构造函数,而这个构造函数中就已经给 _dispatcher 赋值了,因此其所有的子类的初始化之前,_dispatcher 就会被赋值。

protected DispatcherObject()
{
    _dispatcher = Dispatcher.CurrentDispatcher;
}

那么所赋的值是否可能为 null 呢,这就要看 Dispatcher.CurrentDispatcher 是否可能返回一个 null 了。

以下是 Dispatcher.CurrentDispatcher 的属性获取代码:

public static Dispatcher CurrentDispatcher
{
    get
    {
        // Find the dispatcher for this thread.
        Dispatcher currentDispatcher = FromThread(Thread.CurrentThread);;

        // Auto-create the dispatcher if there is no dispatcher for
        // this thread (if we are allowed to).
        if(currentDispatcher == null)
        {
            currentDispatcher = new Dispatcher();
        }

        return currentDispatcher;
    }
}

可以看到,无论前面的方法得到的值是否是 null,后面都会再给 currentDispatcher 局部变量赋值一个新创建的实例的。因此,此属性是绝对不会返回 null 的。

由此可知,DispatcherObject 自构造起便拥有一个不为 nullDispatcher 属性,其所有子类在初始化之前便会得到不为 nullDispatcher 属性。

后续赋值

现在我们来看看在初始化完成之后,后面是否有可能将 _dispatcher 赋值为 null。

_dispatcher 字段的赋值代码仅有两个:

// This method allows certain derived classes to break the dispatcher affinity
// of our objects.
[FriendAccessAllowed] // Built into Base, also used by Framework.
internal void DetachFromDispatcher()
{
    _dispatcher = null;
}

// Make this object a "sentinel" - it can be used in equality tests, but should
// not be used in any other way.  To enforce this and catch bugs, use a special
// sentinel dispatcher, so that calls to CheckAccess and VerifyAccess will
// fail;  this will catch most accidental uses of the sentinel.
[FriendAccessAllowed] // Built into Base, also used by Framework.
internal void MakeSentinel()
{
    _dispatcher = EnsureSentinelDispatcher();
}

第一个 DetachFromDispatcher 很好理解,让 DispatcherObjectDispatcher 无关。在整个 WPF 的代码中,使用此方法的仅有以下 6 处:

  • Freezable.Freeze 实例方法
  • BeginStoryboard.Seal 实例方法
  • Style.Seal 实例方法
  • TriggerBase.Seal 实例方法
  • StyleHelperSealTemplate 静态方法中对 FrameworkTemplate 类型的实例调用此方法
  • ResourceDictionary 在构造函数中为 DispatcherObject 类型的 DummyInheritanceContext 属性调用此方法

Application 类型不是以上任何一个类型的子类(Application 类的直接基类是 DispatcherObject),因此 Application 类中的 Dispatcher 属性不可能因为 DetachFromDispatcher 方法的调用而被赋值为 null

接下来看看 MakeSentinel 方法,此方法的作用不如上面方法那样直观,实际上它的作用仅仅为了验证某个方法调用时所在的线程是否是符合预期的(给 VerifyAccessCheckAccess 使用)。

使用此方法的仅有 1 处:

  • ItemsControl 所用的 ItemInfo 类的静态构造函数
internal static readonly DependencyObject SentinelContainer = new DependencyObject();
internal static readonly DependencyObject UnresolvedContainer = new DependencyObject();
internal static readonly DependencyObject KeyContainer = new DependencyObject();
internal static readonly DependencyObject RemovedContainer = new DependencyObject();

static ItemInfo()
{
    // mark the special DOs as sentinels.  This helps catch bugs involving
    // using them accidentally for anything besides equality comparison.
    SentinelContainer.MakeSentinel();
    UnresolvedContainer.MakeSentinel();
    KeyContainer.MakeSentinel();
    RemovedContainer.MakeSentinel();
}

所有这些使用都与 Application 无关。

结论

总结以上所有的分析:

  1. Application 类型的实例在初始化之前,Dispatcher 属性就已经被赋值且不为 null
  2. 所有可能改变 _dispatcher 属性的常规方法均与 Application 类型无关;

因此,所有常规手段均不会让 Application 类的 Dispatcher 属性拿到 null 值。如果你还说拿到了 null,那就检查是否有逗比程序员通过反射或其他手段将 _dispatcher 字段改为了 null 吧……

Application.Current 静态属性

关于 Application.Current 是否可能为 null 的分析,由于比较长,请参见我的另一篇博客:

  • WPF 的 Application.Current.Dispatcher 中,Current 可能为 null

参考资料

  • DispatcherObject.cs

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 提高使用 Visual Studio 开发效率的键盘快捷键
  • WPF 不要给 Window 类设置变换矩阵(分析篇):System.InvalidOperationException: 转换不可逆。
  • WPF 不要给 Window 类设置变换矩阵(应用篇)
  • git fetch 失败,因为 unable to resolve reference 'refs/remotes/origin/xxx': reference broken
  • .NET/C# 阻止屏幕关闭,阻止系统进入睡眠状态
  • 发现电脑屏幕总是不自动关闭?看看你是否打开了这些程序……
  • 临时编写和调试 C++ 代码?用 VSCode 就够了!一分钟搭好 C++ 调试环境
  • WPF 制作高性能的透明背景异形窗口(使用 WindowChrome 而不要使用 AllowsTransparency=True)
  • 在 WPF 中获取一个依赖对象的所有依赖项属性
  • 如何在 WPF 中获取所有已经显式赋过值的依赖项属性
  • .NET 设计一套高性能的弱事件机制
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • WPF 程序如何跨窗口/跨进程设置控件焦点
  • 使用 SetParent 制作父子窗口的时候,如何设置子窗口的窗口样式以避免抢走父窗口的焦点
  • bootstrap创建登录注册页面
  • CSS 提示工具(Tooltip)
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Java IO学习笔记一
  • Java 最常见的 200+ 面试题:面试必备
  • Less 日常用法
  • Promise面试题,控制异步流程
  • SpringCloud集成分布式事务LCN (一)
  • 从0到1:PostCSS 插件开发最佳实践
  • 对象引论
  • 分布式任务队列Celery
  • 分布式事物理论与实践
  • 记录:CentOS7.2配置LNMP环境记录
  • 面试总结JavaScript篇
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 如何胜任知名企业的商业数据分析师?
  • 使用parted解决大于2T的磁盘分区
  • 数据科学 第 3 章 11 字符串处理
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 在Mac OS X上安装 Ruby运行环境
  • 容器镜像
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • #HarmonyOS:基础语法
  • #HarmonyOS:软件安装window和mac预览Hello World
  • #Linux(make工具和makefile文件以及makefile语法)
  • (12)Hive调优——count distinct去重优化
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (论文阅读40-45)图像描述1
  • (万字长文)Spring的核心知识尽揽其中
  • (转载)利用webkit抓取动态网页和链接
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • /proc/vmstat 详解