Почему в задаче LongRunning (TPL) с JpegBitmapDecoder не хватает ресурсов?

У нас есть управляемое приложение.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(Поток bitmapStream, BitmapC reateOptions createOptions, BitmapCacheOption cacheOption, Guid Ожидается, что ClsId) в System.Windows.Media.Imaging.JpegBitmapDecoder..ctor(Поток bitmapStream, Bit mapCreateOptions createOptions, BitmapCacheOption cacheOption)

Ошибка произошла только тогда, когда мы использовали JpegBitmapDecoder для добавления метаданных. Другими словами, если бы задача просто кодировала и сохраняла растровое изображение в файл, никаких проблем не возникало. Ничего очевидного не было обнаружено при использовании Process Explorer, Process Monitor или других средств диагностики. Никаких утечек потока, памяти или ручки не наблюдалось вообще. Когда возникает такая ошибка, никакие новые приложения не могут быть запущены, например, блокнот, слово и т. Д. Как только наше приложение завершается, все возвращается в нормальное состояние.

Параметр создания задачи LongRunning определен в MSDN как Указывает, что задача будет длительной, грубой операцией. Он дает подсказку TaskScheduler, что переподписка может быть оправдана. Это подразумевает, что поток, выбранный для запуска задачи, может быть не из ThreadPool, т. Е. Он будет создан для цели задачи. Другие параметры создания задачи приведут к тому, что поток ThreadPool будет выбран для задачи.

После некоторого времени анализа и тестирования мы изменили параметр создания задачи на что-то отличное от LongRunning, например, PreferFairness. Никаких других изменений в коде не было сделано вообще. Это "решило" проблему, то есть больше не заканчивалось ошибок хранения.

Мы озадачены фактической причиной, по которой нити LongRunning являются виновниками. Вот некоторые из наших вопросов по этому вопросу:

  1. Почему тот факт, что потоки, выбранные для выполнения задачи, происходят из ThreadPool или нет? Если поток завершается, не должны ли его ресурсы со временем возвращаться GC и возвращаться обратно в ОС, независимо от его происхождения?

  2. Что такого особенного в комбинации задачи LongRunning и функциональности JpegBitmapDecoder, которая вызывает ошибку?

2 ответа

Решение

Занятия в System.Windows.Media.Imaging пространство имен основаны на Dispatcher многопоточная архитектура. Что бы там ни было, частью поведения по умолчанию является запуск нового Dispatcher в каком-либо потоке выполняется всякий раз, когда какой-либо компонент запрашивает текущий диспетчер через статический Dispatcher.Current имущество. Это означает, что вся "среда выполнения" Dispatcher запускается для потока, и выделяются все виды ресурсов, и, если они не очищены должным образом, это приведет к управляемым утечкам. Dispatcher "runtime" также ожидает, что выполняемый поток является потоком STA со стандартной прокачкой сообщений и Task среда выполнения по умолчанию не запускает потоки STA.

Итак, все это говорит о том, почему это происходит с LongRunning, а не с "обычным" потоком на основе ThreadPool? Причина LongRunning означает, что вы каждый раз раскручиваете новый поток, что означает новые ресурсы Dispatcher каждый раз. В конце концов, если вы позволите планировщику заданий по умолчанию (на основе ThreadPool) работать достаточно долго, ему тоже не хватит места, потому что ничего не качает сообщения для Dispatcher время выполнения, чтобы иметь возможность убирать вещи, которые ему нужны, а также.

Поэтому, если вы хотите использовать Dispatcherклассы, основанные на нити, как это, вам действительно нужно сделать это с TaskScheduler который предназначен для выполнения такой работы в пуле потоков, которые управляют Dispatcher "время выполнения" правильно. Хорошая новость в том, что вам повезло, потому что я уже написал одну, которую вы можете найти здесь. Кстати, я использую эту реализацию в трех порциях очень большого объема производственного кода, которые обрабатывают сотни тысяч изображений в день.

Обновление реализации

Недавно я снова обновил реализацию, чтобы она была совместима с новой async Особенности.NET 4.5. Первоначальная реализация не была совместима с SynchronizationContext концепция, потому что это не должно быть. Теперь, когда вы можете использовать await Ключевое слово в C# в методе, который выполняется в потоке Dispatcher, мне нужно иметь возможность сотрудничать с этим. Предыдущая реализация в этой ситуации зашла бы в тупик, а последняя - нет.

Я могу воспроизвести и исправить эту проблему самостоятельно, создавая объекты BitmapSource из Uri. Как и у вас, это происходит, только если TaskCreationOptions.LongRunning.

Чтобы избежать утечки в этой конкретной ситуации, я обнаружил, что вы можете отключить Диспетчер, как только вы создадите экземпляр объекта WPF, который вам нужен.

Вот моя рабочая реализация TaskProc:

private static BitmapImage TaskProc()
{
    var result = new BitmapImage(new Uri(@"c:\test.jpg"));
    // the following line fixes the problem, no more leaks occur
    result.Dispatcher.InvokeShutdown();
    return result;
}
Другие вопросы по тегам