WPF 支持的多线程 UI 并不是线程安全的
Posted lonelyxmas
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF 支持的多线程 UI 并不是线程安全的相关的知识,希望对你有一定的参考价值。
原文:WPF 支持的多线程 UI 并不是线程安全的
WPF 支持创建多个 UI 线程,跨窗口的或者窗口内的都是可以的;但是这个过程并不是线程安全的。
你有极低的概率会遇到 WPF 多线程 UI 的线程安全问题,说直接点就是崩溃。本文将讲述其线程安全问题。
此问题现已报告给微软:Creating multi-thread UI has a low probability to crash · Issue #297 · dotnet/wpf
简述这个线程安全问题
必要条件:
- 创建多个 WPF UI 线程
- 其实两个就够了,一个我们平时写的 App 类所在的主 UI 线程;一个后台 UI 线程,例如用来显示启动闪屏的 UI 线程
- 两个线程的话你需要大量重复试验才能复现;而创建更多线程可以大大提高单次复现概率
- 这些 UI 线程都显示 WPF 窗口
- 无论是 .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:UserslvyiDesktopWalterlv.Bugs.MultiThreadedUIWalterlv.Bugs.MultiThreadedUISplashWindow.xaml:line 1
at Walterlv.Bugs.MultiThreadedUI.SplashWindow..ctor() in C:UserslvyiDesktopWalterlv.Bugs.MultiThreadedUIWalterlv.Bugs.MultiThreadedUISplashWindow.xaml.cs:line 24
at Walterlv.Bugs.MultiThreadedUI.Program.<>c__DisplayClass1_0.<RunSplashWindow>b__0() in C:UserslvyiDesktopWalterlv.Bugs.MultiThreadedUIWalterlv.Bugs.MultiThreadedUIProgram.cs:line 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
下图是 .NET Core 3 版本的 WPF 中在 Visual Studio 2019 抓到的异常:
复现步骤
- 创建一个新的 WPF 项目(无论是 .NET Framework 4.7.2 还是 .NET Core 3)
- 保持自动生成的
App
和MainWindow
不变,我们额外创建一个窗口SplashWindow
。 - 创建一个新的包含 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();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
说明:即便在 new SplashWindow
代码之前调用以下方法修改 SynchronizationContext
也依然会发生异常。
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
- 1
- 2
- 3
我的博客会首发于 https://walterlv.com/,而 CSDN 和博客园仅从其中摘选发布,而且一旦发布了就不再更新。
如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。
以上是关于WPF 支持的多线程 UI 并不是线程安全的的主要内容,如果未能解决你的问题,请参考以下文章
检测是不是在 WPF 和 Winforms 中的 UI 线程上