We have a managed .Net / C# application that creates TPL tasks to perform JPEG metadata encoding on JPEG images. Each task is constructed with TaskCreationOptions.LongRunning option, e.g.,
Task task = new Task( () => TaskProc(), cancelToken, TaskCreationOptions.LongRunning );
TaskProc() utilizes JpegBitmapDecoder and JpegBitmapEncoder classes to add JPEG metadata and save new images to disk. We allow up to 2 such tasks to be active at any one time, and this process should continue indefinitely.
After some time of performing the aforementioned we get Not enough storage is available to process this command exception when trying to create an instance of JpegBitmapDecoder class:
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)
The error occurred only when we utilized JpegBitmapDecoder to add metadata. In other words, if the task would just encode & save a Bitmap image to file, no problems arose. Nothing obvious was revealed when using Process Explorer, Process Monitor, or other diagnostics tools. No thread, memory, or handle leaks were observed at all. When such error occurs, no new applications can be launched, e.g., notepad, word, etc. Once our application is terminated, everything goes back to normal.
The task creation option of LongRunning is defined in MSDN as Specifies that a task will be a long-running, coarse-grained operation. It provides a hint to the TaskScheduler that oversubscription may be warranted. This implies that the thread chosen to run the task may not be from the ThreadPool, i.e., it will be created for the purpose of the task. The other task creation options will result in a ThreadPool thread being selected for the task.
After some time analyzing and testing, we changed the task creation option to anything other than LongRunning, e.g., PreferFairness. No other changes to the code were made at all. This "resolved" the problem, i.e., no more running out of storage errors.
We are puzzled as to the actual reason for LongRunning threads being the culprit. Here are some of our questions on this:
Why should the fact that the threads chosen to execute the task come from the ThreadPool or not? If the thread terminates, shouldn't its resources be reclaimed over time by GC and returned back to the OS, regardless of its origin?
What is so special about the combination of a LongRunning task and JpegBitmapDecoder's functionality that causes the error?