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

标签 c# .net task-parallel-library

我们有一个托管的 .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): Not enough storage is available to process this command at MS.Win32.UnsafeNativeMethods.RegisterClassEx(WNDCLASSEX_D wc_d)
at MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int3 2 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHoo k[] hooks) at System.Windows.Threading.Dispatcher..ctor() at System.Windows.Threading.Dispatcher.get_CurrentDispatcher() at System.Windows.Media.Imaging.BitmapDecoder..ctor(Stream bitmapStream, BitmapC reateOptions createOptions, BitmapCacheOption cacheOption, Guid expectedClsId) at System.Windows.Media.Imaging.JpegBitmapDecoder..ctor(Stream bitmapStream, Bit mapCreateOptions createOptions, BitmapCacheOption cacheOption)

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

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

经过一段时间的分析和测试,我们将任务创建选项更改为 LongRunning 以外的任何选项,例如 PreferFairness。根本没有对代码进行任何其他更改。这“解决”了问题,即不再出现存储空间不足的错误。

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

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

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

最佳答案

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

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

因此,如果您想像这样使用基于 Dispatcher 的线程类,您确实需要使用旨在运行此类任务的自定义 TaskScheduler在正确管理 Dispatcher“运行时”的线程池上工作。好消息是你很幸运,因为我已经写了一个 you can grab here . FWIW,我在每天处理数十万张图像的生产代码的三个非常大的部分中使用此实现。

实现更新

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

关于c# - 为什么使用 JpegBitmapDecoder 的 LongRunning 任务 (TPL) 会耗尽资源?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12624867/

相关文章:

c# - XAML 继承、代码重用、优化

c# - 让一个按钮根据应用程序的状态执行不同的操作?

c# - 我如何做等效的 TaskCompletionSource<nothing>?

c# - 设置默认 CurrentCulture 和 CurrentUICulture(.NET 4.5.2 和 .NET 4.6 之间的差异)

c# - WPF DataGrid 在 AutoGenerateColumns 为 nullable bool 时强制绑定(bind) DataGridCheckBoxColumn

.net - TPL 中的错误 - TaskContinuationOptions.ExecuteSynchronously?

c# - 如何使用 C# 和并行扩展并行化顺序任务?

c# - 如何从 C# 中的字符串中删除双 html <strong> 标记

c# - 为什么有空的 get set 属性而不是使用公共(public)成员变量?

c# - 使用 AForge.Video.FFMPEG/AForge.Video.VFW 截屏视频。帧率问题