为什么使用 JpegBitmapDecoder 的 LongRunning 任务 (TPL) 会耗尽资源?

2023-11-22

我们有一个托管的 .Net / C# 应用程序,它创建 TPL 任务来对 JPEG 图像执行 JPEG 元数据编码。每个任务都是使用 TaskCreationOptions.LongRunning 选项构建的,例如,

Task task = new Task( () => TaskProc(), cancelToken, TaskCreationOptions.LongRunning );

TaskProc() 利用 JpegBitmapDecoder 和 JpegBitmapEncoder 类添加 JPEG 元数据并将新图像保存到磁盘。 我们允许同时最多有 2 个此类任务处于活动状态,并且此过程应无限期地持续下去。

执行上述一段时间后,我们得到没有足够的可用存储空间 处理这个命令尝试创建 JpegBitmapDecoder 类的实例时出现异常:

System.ComponentModel.Win32Exception(0x80004005):存储空间不足 可在以下位置处理此命令 MS.Win32.UnsafeNativeMethods.RegisterClassEx(WNDCLASSEX_D wc_d)
在 MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle、Int3 2 x、Int32 y、Int32 宽度、Int32 高度、字符串名称、 IntPtr 父级,HwndWrapperHoo k[] 挂钩)位于 System.Windows.Threading.Dispatcher..ctor() 位于 System.Windows.Threading.Dispatcher.get_CurrentDispatcher() 在 System.Windows.Media.Imaging.BitmapDecoder..ctor(流位图流, BitmapC reateOptions createOptions、BitmapCacheOption cacheOption、 指导预期ClsId)位于 System.Windows.Media.Imaging.JpegBitmapDecoder..ctor(流 bitmapStream、Bit mapCreateOptions createOptions、BitmapCacheOption 缓存选项)

发生错误only当我们使用 JpegBitmapDecoder 添加元数据时。换句话说,如果任务只是将位图图像编码并保存到文件中,则不会出现问题。使用 Process Explorer、Process Monitor 或其他诊断工具时,没有发现任何明显的情况。根本没有观察到线程、内存或句柄泄漏。出现此类错误时,无法启动新的应用程序,例如记事本、word等。 一旦我们的应用程序终止,一切都会恢复正常。

LongRunning的任务创建选项在MSDN中定义为指定任务将是长时间运行的粗粒度操作。它向任务调度程序提供了一个提示:超额订阅可能是有道理的。这意味着选择运行任务的线程可能不是来自线程池,即它将是为了任务的目的而创建的。其他任务创建选项将导致为任务选择 ThreadPool 线程。

经过一段时间的分析和测试,我们将任务创建选项更改为除长跑, e.g., 偏好公平。根本没有对代码进行任何其他更改。这“解决”了问题,即不再出现存储空间不足的错误。

我们对 LongRunning 线程成为罪魁祸首的真正原因感到困惑。 以下是我们对此的一些问题:

  1. 为什么选择执行任务的线程应该来自线程池?如果线程终止,那么它的资源是否不应该随着时间的推移由 GC 回收并返回到操作系统,无论其来源如何?

  2. LongRunning 任务和 JpegBitmapDecoder 功能的组合有什么特别之处会导致错误?


课程在System.Windows.Media.Imaging命名空间基于the Dispatcher线程架构。无论好坏,默认行为的一部分是启动一个新的Dispatcher每当某个组件通过静态请求当前调度程序时,在任何正在执行的线程上Dispatcher.Current财产。这意味着整个调度程序“运行时”都会为线程启动,并分配各种资源,如果没有正确清理,将导致托管泄漏。这Dispatcher“运行时”还期望其执行的线程是一个 STA 线程,并且正在进行标准消息泵送Task默认情况下,运行时不会启动 STA 线程。

那么,综上所述,为什么会发生在 LongRunning 而不是基于“常规”ThreadPool 的线程呢?因为 LongRunning 意味着您每次都会启动一个新线程,这意味着每次都会有新的调度程序资源。最终,如果您让默认任务调度程序(基于线程池的任务调度程序)运行足够长的时间,它也会耗尽空间,因为没有任何东西可以为任务泵送消息。Dispatcher运行时也能够清理它需要的东西。

因此,如果您想使用Dispatcher- 像这样的基于线程的类,您确实需要使用自定义来执行此操作TaskScheduler旨在在管理线程池上运行此类工作Dispatcher正确地“运行时”。好消息是你很幸运,因为我已经写了一篇你可以在这里抓住。 FWIW,我在生产代码的三个非常大容量的部分中使用了这种实现,每天处理数十万张图像。

实施更新

我最近再次更新了实现,以便它与新版本兼容async.NET 4.5 的功能。最初的实施不与SynchronizationContext概念,因为它不必如此。现在您可能正在使用await在 Dispatcher 线程上执行的方法中的 C# 关键字,我需要能够与之合作。以前的实现在这种情况下会死锁,而最新的实现则不会。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么使用 JpegBitmapDecoder 的 LongRunning 任务 (TPL) 会耗尽资源? 的相关文章

随机推荐