C# 在后台线程中创建可释放对象时发生资源泄漏
在我的应用程序中,我在后台(线程池)线程中创建C# 在后台线程中创建可释放对象时发生资源泄漏,c#,wpf,multithreading,C#,Wpf,Multithreading,在我的应用程序中,我在后台(线程池)线程中创建Freezable对象,冻结它们,然后在主线程上显示它们。一切都正常,只是过了一段时间,整个系统变得迟钝,应用程序最终崩溃 我已设法将问题减少到这一行: var temp = new DrawingGroup(); 如果在不同的后台(非UI)线程上频繁运行,整个系统就会变得迟钝,最终应用程序崩溃 (在我的实际应用程序中,我会在这个对象上画一些东西,冻结它,然后在主线程上显示它,但这不是重现问题的必要条件。) 复制该问题的完整代码(复制到默认的空白w
Freezable
对象,冻结它们,然后在主线程上显示它们。一切都正常,只是过了一段时间,整个系统变得迟钝,应用程序最终崩溃
我已设法将问题减少到这一行:
var temp = new DrawingGroup();
如果在不同的后台(非UI)线程上频繁运行,整个系统就会变得迟钝,最终应用程序崩溃
(在我的实际应用程序中,我会在这个对象上画一些东西,冻结它,然后在主线程上显示它,但这不是重现问题的必要条件。)
复制该问题的完整代码(复制到默认的空白wpf应用程序中):
我认为正在发生的是:
DrawingGroup
是从DependencyObject
派生的,DependencyObject
的构造函数使用Dispatcher.CurrentDispatcher
,然后为该线程创建一个新的Dispatcher
李>
HwndWrapper
的终结代码,我认为HwndWrapper
尝试使用Dispatcher.BeginInvoke
同步它自己的清理- 显然,使用
或线程池
而不是手动创建线程会延迟此问题。但是任务
也会随着时间的推移创建和关闭新线程,因此这只会延迟问题,而不是解决方案ThreadPool
- 在每个线程的末尾强制执行完整的GC收集不会改变任何事情。这与垃圾收集的不确定性无关
- 在后台线程结束时手动调用
似乎可行,但我不知道如何确保在每个Dispatcher.InvokeShutdown
线程结束时调用它。没有编写自己的ThreadPool
,即线程池
TPL
或ThreadPool
运行此逻辑对吗?如果是这样,最后一个案例是您的一个选项,那么您可以轻松地获取
DrawingGroup
的属性,并在finally
块中调用它的方法
所以你可以这样写:
DrawingGroup temp;
try
{
temp = new DrawingGroup();
}
finally
{
// do the work
if (temp != null)
{
temp.Dispatcher.InvokeShutdown();
}
}
这是.NET中调度器系统设计的一个已知缺陷。它影响依赖于Dispatcher的WPF和非WPF库。微软已经表示这一问题不会得到解决 它与冷冻操作或任何操作的使用无关。从
DependencyObject
派生的任何对象(类)都将具有一个基本构造函数,该构造函数将触发为该线程创建Dispatcher
实例(如果以前没有创建过)。换句话说,Dispatcher
在设计上是一个线程本地单例
当足够多的Dispatcher
实例泄漏时,就会发生崩溃。这意味着在应用程序的生命周期中创建和销毁了相同数量的线程,每个线程都创建了一个或多个DependencyObject
。询问任何一位应用程序开发人员,他们都会说这是不常见的,虽然本身并不坏,但在设计一个需要创建和销毁许多线程的应用程序时肯定需要特别小心
在开始之前,这里提供了一种安全的方法来查询
调度程序,,如果它以前不存在,则不会导致自动创建一个调度程序
Thread currentThread = Thread.CurrentThread;
Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);
首先,您可以在使用线程后关闭dispatcher
Thread currentThread = Thread.CurrentThread;
Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);
其次,要意识到一旦一个线程关闭了它,就不可能为同一个线程重新初始化调度程序
。换句话说,在InvokeShutdown
之后,不可能在该线程上使用WPF或依赖于Dispatcher
的任何其他库。这根线实际上被毒死了
结合第一点和第二点可以得出结论,您需要自己的线程池,每个线程池都有一个调度程序。只要控制线程池的下风,就不会有泄漏的危险
有一些流行的开源.NET线程池库,可以与.NET系统线程池并行运行(独立运行)。这是解决此特定平台问题的适当方法
如果您同时控制前端(表示层)和后端(图像渲染),则有一种更简单、更严格、更有效的方法(尽管未充分利用):
- 使调用者必须初始化调度程序成为策略;后端只会检查调度程序是否已经存在(通过
dispatcher.FromThread
),如果没有,则拒绝执行工作
这种方法将负担转移到表示层,具有讽刺意味的是,表示层往往已经初始化了调度器
这种方法也适用于一个线程池的一个+1好主意,但我不确定它是否“合法”:线程池线程可能会再次执行相同的代码,但它不会(总是)创建一个新的调度程序
——它可能会使用“disposed”实例。我不确定什么DrawingGroup
(或任何Freezable
)需要一个调度程序
,但与半清理的调度程序
一起使用是否有危险?您应该在不需要的时候清除调度程序
Thread currentThread = Thread.CurrentThread;
Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);