Невозможно создать больше Диспетчера. Закончились ресурсы?

В нашем приложении мы используем 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();
Другие вопросы по тегам