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

WPF 支持的多线程 UI 并不是线程安全的

WPF 支持创建多个 UI 线程,跨窗口的或者窗口内的都是可以的;但是这个过程并不是线程安全的。

你有极低的概率会遇到 WPF 多线程 UI 的线程安全问题,说直接点就是崩溃。本文将讲述其线程安全问题。

此问题现已报告给微软:Creating multi-thread UI has a low probability to crash · Issue #297 · dotnet/wpf


本文内容

      • 简述这个线程安全问题
      • 复现步骤

简述这个线程安全问题

必要条件:

  1. 创建多个 WPF UI 线程
    • 其实两个就够了,一个我们平时写的 App 类所在的主 UI 线程;一个后台 UI 线程,例如用来显示启动闪屏的 UI 线程
    • 两个线程的话你需要大量重复试验才能复现;而创建更多线程可以大大提高单次复现概率
  2. 这些 UI 线程都显示 WPF 窗口
  3. 无论是 .NET Framework 4.7.2 版本的 WPF,还是 .NET Core 3 版本的 WPF 都会出现此问题

现象:

  • 抛出异常,程序崩溃

比如下面是其中一种异常:

Exception thrown: 'System.NullReferenceException' in WindowsBase.dll
Object reference not set to an instance of an object.

System.NullReferenceException: Object reference not set to an instance of an object.
   at System.IO.Packaging.PackagePart.CleanUpRequestedStreamsList()
   at System.IO.Packaging.PackagePart.GetStream(FileMode mode, FileAccess access)
   at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
   at Walterlv.Bugs.MultiThreadedUI.SplashWindow.InitializeComponent() in C:\Users\lvyi\Desktop\Walterlv.Bugs.MultiThreadedUI\Walterlv.Bugs.MultiThreadedUI\SplashWindow.xaml:line 1
   at Walterlv.Bugs.MultiThreadedUI.SplashWindow..ctor() in C:\Users\lvyi\Desktop\Walterlv.Bugs.MultiThreadedUI\Walterlv.Bugs.MultiThreadedUI\SplashWindow.xaml.cs:line 24
   at Walterlv.Bugs.MultiThreadedUI.Program.<>c__DisplayClass1_0.<RunSplashWindow>b__0() in C:\Users\lvyi\Desktop\Walterlv.Bugs.MultiThreadedUI\Walterlv.Bugs.MultiThreadedUI\Program.cs:line 33

下图是 .NET Core 3 版本的 WPF 中在 Visual Studio 2019 抓到的异常:

异常

复现步骤

  1. 创建一个新的 WPF 项目(无论是 .NET Framework 4.7.2 还是 .NET Core 3)
  2. 保持自动生成的 AppMainWindow 不变,我们额外创建一个窗口 SplashWindow
  3. 创建一个新的包含 Main 函数的 Program 类,并在项目属性中设置 Program 为启动对象(替代 App)。

项目结构

其他文件全部保持 Visual Studio 生成的默认代码不变,而 Program.cs 的代码如下:

using System;
using System.Threading;
using System.Windows.Threading;

namespace Walterlv.Bugs.MultiThreadedUI
{
    public class Program
    {
        [STAThread]
        private static void Main(string[] args)
        {
            for (var i = 0; i < 50; i++)
            {
                RunSplashWindow(i);
            }

            var app = new App();
            app.InitializeComponent();
            app.Run();
        }

        private static void RunSplashWindow(int index)
        {
            var thread = new Thread(() =>
            {
                var window = new SplashWindow
                {
                    Title = $"SplashWindow {index.ToString().PadLeft(2, ' ')}",
                };
                window.Show();
                Dispatcher.Run();
            })
            {
                IsBackground = true,
            };
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }
    }
}

说明:即便在 new SplashWindow 代码之前调用以下方法修改 SynchronizationContext 也依然会发生异常。

SynchronizationContext.SetSynchronizationContext(
    new DispatcherSynchronizationContext(
        Dispatcher.CurrentDispatcher));

我的博客会首发于 https://walterlv.com/,而 CSDN 和博客园仅从其中摘选发布,而且一旦发布了就不再更新。

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

知识共享许可协议

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

相关文章:

  • ReSharper 在 C 盘占用了太多空间了,本文告诉你如何安全地删除或转移这些文件
  • WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了
  • git 的合并原理(递归三路合并算法)
  • git 合并策略
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • 使用一句 git 命令将仓库的改动推送到所有的远端
  • 将 svn 仓库迁移到 git 仓库
  • 使用 Visual Studio 调试多进程的程序
  • 如何更精准地设置 C# / .NET Core 项目的输出路径?(包括添加和删除各种前后缀)
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • Windows 上的应用程序在运行期间可以给自己改名(可以做 OTA 自我更新)
  • 为 WPF 程序添加 Windows 跳转列表的支持
  • 在 Windows 系统上降低 UAC 权限运行程序(从管理员权限降权到普通用户权限)
  • 专栏素材
  • 自己简单写的 事件订阅机制
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • angular2 简述
  • github指令
  • HTTP那些事
  • Java多线程(4):使用线程池执行定时任务
  • laravel with 查询列表限制条数
  • linux学习笔记
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • nfs客户端进程变D,延伸linux的lock
  • Spring Boot快速入门(一):Hello Spring Boot
  • supervisor 永不挂掉的进程 安装以及使用
  • Web设计流程优化:网页效果图设计新思路
  • 阿里云ubuntu14.04 Nginx反向代理Nodejs
  • 阿里云购买磁盘后挂载
  • 百度地图API标注+时间轴组件
  • 区块链共识机制优缺点对比都是什么
  • 双管齐下,VMware的容器新战略
  • 微信小程序开发问题汇总
  • 我感觉这是史上最牛的防sql注入方法类
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • #define与typedef区别
  • #预处理和函数的对比以及条件编译
  • #在 README.md 中生成项目目录结构
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (ZT)薛涌:谈贫说富
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转)socket Aio demo
  • . NET自动找可写目录
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout
  • @property括号内属性讲解
  • @RestController注解的使用
  • [ JavaScript ] JSON方法