在我的应用程序中,我在后台(线程池)线程中创建Freezable
个对象,冻结它们,然后在主线程上显示它们。一切正常,除了一段时间后,整个系统变得迟钝,应用程序最终崩溃。
我设法将问题减少到这一行:
var temp = new DrawingGroup();
如果你经常在不同的后台(非UI)线程上运行,整个系统会变得迟钝,最终应用程序崩溃。
(在我的实际应用程序中,我然后在这个对象上绘制一些东西,冻结它,然后在主线程上显示它,但是没有必要重现这个问题。)
重现该问题的完整代码(复制到默认的空白wpf应用程序中):
public partial class MainWindow : Window
{
private DispatcherTimer dt;
public MainWindow()
{
InitializeComponent();
dt = new DispatcherTimer();
dt.Interval = TimeSpan.FromSeconds(0.1);
dt.Tick += dt_Tick;
dt.IsEnabled = true;
}
private int counter = 0;
void dt_Tick(object sender, EventArgs e)
{
for (int i = 0; i < 100; i++)
{
var thread = new Thread(MemoryLeakTest);
thread.Start();
}
Title = string.Format("Mem leak test {0}", counter++);
}
private void MemoryLeakTest()
{
try
{
var temp = new DrawingGroup();
temp.Freeze();
}
catch (Exception e)
{
dt.IsEnabled = false;
MessageBox.Show(e.Message+Environment.NewLine+e.StackTrace);
}
}
}
在大约150次计时器运行后(即在短时间内创建了大约15000个线程之后),我得到了这个例外:
Not enough storage is available to process this command
bei MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook[] hooks)
bei System.Windows.Threading.Dispatcher..ctor()
bei System.Windows.DependencyObject..ctor()
bei System.Windows.Media.DrawingGroup..ctor()
bei WpfApplication5.MainWindow.MemoryLeakTest() in ...
我认为发生的事情是:
DrawingGroup
派生自DependencyObject
,DependencyObject
的构造函数使用Dispatcher.CurrentDispatcher
,然后为此主题创建新的Dispatcher
。 HwndWrapper
的终结代码,我认为HwndWrapper
会尝试使用Dispatcher.BeginInvoke
同步自己的清理。有没有办法解决或解决这个问题?
到目前为止我尝试过:
ThreadPool
或Tasks
而不是手动创建线程会延迟此问题。但ThreadPool
也会随着时间的推移创建并关闭新线程,因此只会延迟问题,而不是解决方案。Dispatcher.InvokeShutdown
似乎有效,但我不知道如何确保在每个ThreadPool
线程结束时调用它。没有写我自己的ThreadPool
,那就是...... 答案 0 :(得分:1)
我是否正确,您使用TPL
或ThreadPool
运行此逻辑?
如果是这样,最后一个案例是您的选项,您可以轻松获取DrawingGroup
Dispatcher属性,并在InvokeShutdown
方法中调用它http://www.nerdparadise.com/tech/python/pygame/basics/part3/ {1}}阻止。
所以你可以这样写:
finally
答案 1 :(得分:1)
这是.NET中Dispatcher系统设计的一个已知缺陷。它会影响依赖于Dispatcher的WPF和非WPF库。微软表示不会修复此问题。
它与使用冷冻操作或任何操作无关。从DependencyObject
派生的任何对象(类)都将具有 基础构造函数 ,用于触发为该线程创建Dispatcher
实例,如果之前没有创建过。换句话说,Dispatcher
是一个线程局部单例设计。
当Dispatcher
的足够(数万)实例泄露时,就会发生崩溃。这意味着在应用程序的生命周期中创建并销毁了相同数量的线程,每个线程都创建了一个或多个DependencyObject
。询问任何应用程序开发人员,他们会说 不常见,但本身并不坏,但肯定需要特别小心 来设计一个有很多应用程序的应用程序线程已被创建和销毁。
在开始之前,这是一种安全的方法来查询Dispatcher
, ,如果在 之前不存在则会自动创建一个
Thread currentThread = Thread.CurrentThread;
Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);
MSDN: Dispatcher.FromThread
method
首先,您可以在完成 主题 后关闭调度程序。
MSDN: Dispatcher.InvokeShutdown
method
其次,要意识到一旦关闭了一个 线程 ,就不可能为同一个线程重新初始化Dispatcher
。换句话说,在InvokeShutdown
之后,无法在该线程上使用WPF或依赖于Dispatcher
的任何其他库。该线程有效中毒致死。
结合第一点和第二点可以得出结论:您需要自己的线程池,每个线程池都赋予Dispatcher
。只要您控制线程池的风向下,就没有泄漏的危险。
有一些流行的开源.NET线程池库,可以与.NET系统线程池一起运行(独立于)。这是解决此特定平台问题的适当方式。
如果您控制前端(表示层)和后端(图像渲染),则 更简单,更严格,更有效 (虽然利用不足)方法:
Dispatcher.FromThread
), 拒绝执行 如果不是这种方法将负担转移到表示层,具有讽刺意味的是,Dispatcher已经初始化了。
此方法也适用于 一个 的线程池。