Невозможно создать больше Диспетчера. Закончились ресурсы?
В нашем приложении мы используем PngBitmapEncoder для кодирования и сохранения изображения PNG в отдельном потоке \ задаче. После нескольких дней запуска приложения мы видим, что Dispatcher не может быть создан из Encoder и выдает ошибку
Недостаточно памяти для обработки команды
И имеет стек вызовов ниже
System.ComponentModel.Win32Exception (0x80004005): Not enough storage is available to process this command
at MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook[] hooks)
at System.Windows.Threading.Dispatcher..ctor()
at System.Windows.Threading.DispatcherObject..ctor()
at System.Windows.Media.Imaging.BitmapEncoder..ctor(Boolean isBuiltIn)
Поскольку.Net доступен с открытым исходным кодом, мне стало любопытно, какая строка внутри конструктора Dispatcher выдает ошибку
[SecurityCritical, SecurityTreatAsSafe]
private Dispatcher()
{
_queue = new PriorityQueue<DispatcherOperation>();
_tlsDispatcher = this; // use TLS for ownership only
_dispatcherThread = Thread.CurrentThread;
// Add ourselves to the map of dispatchers to threads.
lock(_globalLock)
{
_dispatchers.Add(new WeakReference(this));
}
_unhandledExceptionEventArgs = new DispatcherUnhandledExceptionEventArgs(this);
_exceptionFilterEventArgs = new DispatcherUnhandledExceptionFilterEventArgs(this);
_defaultDispatcherSynchronizationContext = new DispatcherSynchronizationContext(this);
// Create the message-only window we use to receive messages
// that tell us to process the queue.
MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper();
_window = new SecurityCriticalData<MessageOnlyHwndWrapper>( window );
_hook = new HwndWrapperHook(WndProcHook);
_window.Value.AddHook(_hook);
// DDVSO:447590
// Verify that the accessibility switches are set prior to any major UI code running.
AccessibilitySwitches.VerifySwitches(this);
}
Обновить
Обновлен код конструктора из открытого исходного кода.net. Файл dispatcher.cs доступен здесь https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs,078d6b27d9837a35
После некоторого расследования мы обнаружили, что проблема возникает примерно после 15000 итераций (каждая итерация создает новый поток и вызывает PngBitmapEncoder). Затем выяснилось, что это связано с пределом глобальной таблицы атомов (0x4000 или 16384). Подробнее о Global Atom Table здесь https://docs.microsoft.com/en-us/archive/blogs/ntdebugging/identifying-global-atom-table-leaks
Диспетчер, создаваемый каждый раз, делает запись в таблице глобальных атомов, и при выходе из потока эта запись не очищается. Это приводит к утечке в таблице глобальных атомов, и когда она достигает максимального предела, выдается ошибка "Недостаточно памяти...". Это похоже на проблему с обработкой Microsoft Dispatcher. Даже в документации PngBitmapEncoder я не вижу никаких замечаний относительно обработки Dispatcher и любого явного отключения диспетчера.
1 ответ
У меня была эта проблема несколько лет назад при выполнении фоновой обработки с использованием объектов в System.Windows.Media.Imaging
пространство имен тоже. Я наткнулся на запись в блоге инженера Microsoft, который признал, что это проблема, но не было достаточного интереса, чтобы решить ее или что-то в этом роде. Я помню, как надеялся, что они исправят это в поправках к фреймворку. Инженер опубликовал решение, которое мне подходит.
Следует упомянуть, что я пробовал использовать решение в пуле потоков, используя System.Threading.ThreadPool.QueueUserWorkItem()
или System.Threading.Tasks.Task.Run()
, но обнаружили, что решение не работает в пуле потоков; возможно потому, что поток используется повторно. Единственный способ решить эту проблему - использоватьSystem.Threading.Thread
делать работу. Ниже приводится основная идея о том, как обойти проблему и принудительно освободить ресурс.
new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
// Do some imaging work.
// This asks the dispatcher associated with this thread to shut down right away.
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Normal);
System.Windows.Threading.Dispatcher.Run();
})).Start();